mirror of
https://github.com/siderolabs/talos.git
synced 2026-05-05 12:26:21 +02:00
fix: properly handle YAML comment stripping for multi-doc
Fixes #7425 The previously used method doesn't handle YAML multi-doc, incorrectly stripping only the first document and throwing away everything else. Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
This commit is contained in:
parent
c02ada7d95
commit
e241be85ba
@ -19,11 +19,11 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
"gopkg.in/yaml.v3"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/cmd/util/editor"
|
||||
"k8s.io/kubectl/pkg/cmd/util/editor/crlf"
|
||||
|
||||
"github.com/siderolabs/talos/cmd/talosctl/pkg/talos/helpers"
|
||||
"github.com/siderolabs/talos/cmd/talosctl/pkg/talos/yamlstrip"
|
||||
"github.com/siderolabs/talos/pkg/machinery/api/machine"
|
||||
"github.com/siderolabs/talos/pkg/machinery/client"
|
||||
"github.com/siderolabs/talos/pkg/machinery/constants"
|
||||
@ -111,7 +111,7 @@ func editFn(c *client.Client) func(context.Context, string, resource.Resource, e
|
||||
edited = stripEditingComment(edited)
|
||||
|
||||
// If we're retrying the loop because of an error, and no change was made in the file, short-circuit
|
||||
if lastError != "" && bytes.Equal(cmdutil.StripComments(editedDiff), cmdutil.StripComments(edited)) {
|
||||
if lastError != "" && bytes.Equal(yamlstrip.Comments(editedDiff), yamlstrip.Comments(edited)) {
|
||||
if _, err = os.Stat(path); !os.IsNotExist(err) {
|
||||
message := addEditingComment(lastError)
|
||||
message += fmt.Sprintf("A copy of your changes has been stored to %q\nEdit canceled, no valid changes were saved.\n", path)
|
||||
@ -120,7 +120,7 @@ func editFn(c *client.Client) func(context.Context, string, resource.Resource, e
|
||||
}
|
||||
}
|
||||
|
||||
if len(bytes.TrimSpace(bytes.TrimSpace(cmdutil.StripComments(edited)))) == 0 {
|
||||
if len(bytes.TrimSpace(bytes.TrimSpace(yamlstrip.Comments(edited)))) == 0 {
|
||||
fmt.Fprintln(os.Stderr, "Apply was skipped: empty file.")
|
||||
|
||||
break
|
||||
|
||||
@ -15,9 +15,9 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
"gopkg.in/yaml.v3"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
|
||||
"github.com/siderolabs/talos/cmd/talosctl/pkg/talos/helpers"
|
||||
"github.com/siderolabs/talos/cmd/talosctl/pkg/talos/yamlstrip"
|
||||
"github.com/siderolabs/talos/pkg/machinery/api/machine"
|
||||
"github.com/siderolabs/talos/pkg/machinery/client"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/configpatcher"
|
||||
@ -67,8 +67,8 @@ func patchFn(c *client.Client, patches []configpatcher.Patch) func(context.Conte
|
||||
})
|
||||
|
||||
if bytes.Equal(
|
||||
bytes.TrimSpace(cmdutil.StripComments(patched)),
|
||||
bytes.TrimSpace(cmdutil.StripComments(body)),
|
||||
bytes.TrimSpace(yamlstrip.Comments(patched)),
|
||||
bytes.TrimSpace(yamlstrip.Comments(body)),
|
||||
) {
|
||||
fmt.Fprintln(os.Stderr, "Apply was skipped: no changes detected.")
|
||||
|
||||
|
||||
4
cmd/talosctl/pkg/talos/yamlstrip/testdata/malformed.in.yaml
vendored
Normal file
4
cmd/talosctl/pkg/talos/yamlstrip/testdata/malformed.in.yaml
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
data:
|
||||
# This is a comment
|
||||
some:
|
||||
other: a: b: c
|
||||
3
cmd/talosctl/pkg/talos/yamlstrip/testdata/malformed.out.yaml
vendored
Normal file
3
cmd/talosctl/pkg/talos/yamlstrip/testdata/malformed.out.yaml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
data:
|
||||
some:
|
||||
other: a: b: c
|
||||
18
cmd/talosctl/pkg/talos/yamlstrip/testdata/multidoc.in.yaml
vendored
Normal file
18
cmd/talosctl/pkg/talos/yamlstrip/testdata/multidoc.in.yaml
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
# siderolink config
|
||||
apiVersion: v1alpha1
|
||||
kind: SideroLinkConfig # kind of the document
|
||||
# apiUrl is the URL of the SideroLink API endpoint
|
||||
apiUrl: grpc://172.20.0.1:4000/?jointoken=foo
|
||||
---
|
||||
apiVersion: v1alpha1
|
||||
kind: KmsgLogConfig
|
||||
name: apiSink # named document
|
||||
url: tcp://[fdae:41e4:649b:9303::1]:4001/
|
||||
options: # options are optional
|
||||
# more options
|
||||
foo: bar # this option
|
||||
---
|
||||
apiVersion: v1alpha1
|
||||
kind: EventSinkConfig
|
||||
endpoint: "[fdae:41e4:649b:9303::1]:8080"
|
||||
# end of document
|
||||
14
cmd/talosctl/pkg/talos/yamlstrip/testdata/multidoc.out.yaml
vendored
Normal file
14
cmd/talosctl/pkg/talos/yamlstrip/testdata/multidoc.out.yaml
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
apiVersion: v1alpha1
|
||||
kind: SideroLinkConfig
|
||||
apiUrl: grpc://172.20.0.1:4000/?jointoken=foo
|
||||
---
|
||||
apiVersion: v1alpha1
|
||||
kind: KmsgLogConfig
|
||||
name: apiSink
|
||||
url: tcp://[fdae:41e4:649b:9303::1]:4001/
|
||||
options:
|
||||
foo: bar
|
||||
---
|
||||
apiVersion: v1alpha1
|
||||
kind: EventSinkConfig
|
||||
endpoint: "[fdae:41e4:649b:9303::1]:8080"
|
||||
87
cmd/talosctl/pkg/talos/yamlstrip/yamlstrip.go
Normal file
87
cmd/talosctl/pkg/talos/yamlstrip/yamlstrip.go
Normal file
@ -0,0 +1,87 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package yamlstrip provides YAML file manipulation.
|
||||
package yamlstrip
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Comments strips comments from a YAML file.
|
||||
//
|
||||
// If the YAML file is parseable, it will be accurately stripped. Otherwise, it
|
||||
// will be stripped in a best-effort manner.
|
||||
func Comments(b []byte) []byte {
|
||||
stripped, err := stripViaDecoding(b)
|
||||
if err != nil {
|
||||
stripped = stripManual(b)
|
||||
}
|
||||
|
||||
return stripped
|
||||
}
|
||||
|
||||
func stripViaDecoding(b []byte) ([]byte, error) {
|
||||
var out bytes.Buffer
|
||||
|
||||
decoder := yaml.NewDecoder(bytes.NewReader(b))
|
||||
encoder := yaml.NewEncoder(&out)
|
||||
|
||||
for {
|
||||
var node yaml.Node
|
||||
|
||||
err := decoder.Decode(&node)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
removeComments(&node)
|
||||
|
||||
if err = encoder.Encode(&node); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
func removeComments(node *yaml.Node) {
|
||||
node.FootComment = ""
|
||||
node.HeadComment = ""
|
||||
node.LineComment = ""
|
||||
|
||||
for _, child := range node.Content {
|
||||
removeComments(child)
|
||||
}
|
||||
}
|
||||
|
||||
func stripManual(b []byte) []byte {
|
||||
stripped := []byte{}
|
||||
lines := bytes.Split(b, []byte("\n"))
|
||||
|
||||
for i, line := range lines {
|
||||
trimline := bytes.TrimSpace(line)
|
||||
|
||||
// this is not accurate, but best effort
|
||||
if bytes.HasPrefix(trimline, []byte("#")) && !bytes.HasPrefix(trimline, []byte("#!")) {
|
||||
continue
|
||||
}
|
||||
|
||||
stripped = append(stripped, line...)
|
||||
|
||||
if i < len(lines)-1 {
|
||||
stripped = append(stripped, '\n')
|
||||
}
|
||||
}
|
||||
|
||||
return stripped
|
||||
}
|
||||
36
cmd/talosctl/pkg/talos/yamlstrip/yamlstrip_test.go
Normal file
36
cmd/talosctl/pkg/talos/yamlstrip/yamlstrip_test.go
Normal file
@ -0,0 +1,36 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package yamlstrip_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/siderolabs/talos/cmd/talosctl/pkg/talos/yamlstrip"
|
||||
)
|
||||
|
||||
func TestComments(t *testing.T) {
|
||||
testCases, err := filepath.Glob(filepath.Join("testdata", "*.in.yaml"))
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, path := range testCases {
|
||||
path := path
|
||||
|
||||
t.Run(filepath.Base(path), func(t *testing.T) {
|
||||
in, err := os.ReadFile(path)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected, err := os.ReadFile(strings.ReplaceAll(path, ".in.yaml", ".out.yaml"))
|
||||
require.NoError(t, err)
|
||||
|
||||
out := yamlstrip.Comments(in)
|
||||
require.Equal(t, string(expected), string(out))
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user