Repository: lima-vm/lima
Pull request #2601 (2024-09-09 18:07:59)

Validate yaml with yqlib and not with go-yaml v3
Just in case that yqlib starts using another library...

Issue #2597


Patch:
diff --git a/go.mod b/go.mod
index d8eaf6c6..7d629cbc 100644
--- a/go.mod
+++ b/go.mod
@@ -45,7 +45,6 @@ require (
 	google.golang.org/grpc v1.66.0
 	google.golang.org/protobuf v1.34.2
 	gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
-	gopkg.in/yaml.v3 v3.0.1
 	gotest.tools/v3 v3.5.1
 	inet.af/tcpproxy v0.0.0-20221017015627-91f861402626
 	k8s.io/api v0.31.0
@@ -121,6 +120,7 @@ require (
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 	gvisor.dev/gvisor v0.0.0-20231023213702-2691a8f9b1cf // indirect
 	k8s.io/klog/v2 v2.130.1 // indirect
 	k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
diff --git a/pkg/limayaml/load.go b/pkg/limayaml/load.go
index 20670eb6..7aeba343 100644
--- a/pkg/limayaml/load.go
+++ b/pkg/limayaml/load.go
@@ -9,8 +9,8 @@ import (
 	"github.com/goccy/go-yaml"
 	"github.com/lima-vm/lima/pkg/store/dirnames"
 	"github.com/lima-vm/lima/pkg/store/filenames"
+	"github.com/lima-vm/lima/pkg/yqutil"
 	"github.com/sirupsen/logrus"
-	yamlv3 "gopkg.in/yaml.v3"
 )
 
 func unmarshalDisk(dst *Disk, b []byte) error {
@@ -22,24 +22,13 @@ func unmarshalDisk(dst *Disk, b []byte) error {
 	return yaml.Unmarshal(b, dst)
 }
 
-func (d *Disk) UnmarshalYAML(value *yamlv3.Node) error {
-	var v interface{}
-	if err := value.Decode(&v); err != nil {
-		return err
-	}
-	if s, ok := v.(string); ok {
-		*d = Disk{Name: s}
-	}
-	return nil
-}
-
 func unmarshalYAML(data []byte, v interface{}, comment string) error {
 	if err := yaml.UnmarshalWithOptions(data, v, yaml.DisallowDuplicateKey(), yaml.CustomUnmarshaler[Disk](unmarshalDisk)); err != nil {
 		return fmt.Errorf("failed to unmarshal YAML (%s): %w", comment, err)
 	}
 	// the go-yaml library doesn't catch all markup errors, unfortunately
 	// make sure to get a "second opinion", using the same library as "yq"
-	if err := yamlv3.Unmarshal(data, v); err != nil {
+	if err := yqutil.ValidateContent(data); err != nil {
 		return fmt.Errorf("failed to unmarshal YAML (%s): %w", comment, err)
 	}
 	if err := yaml.UnmarshalWithOptions(data, v, yaml.Strict(), yaml.CustomUnmarshaler[Disk](unmarshalDisk)); err != nil {
diff --git a/pkg/yqutil/yqutil.go b/pkg/yqutil/yqutil.go
index e8628adc..c1798a3a 100644
--- a/pkg/yqutil/yqutil.go
+++ b/pkg/yqutil/yqutil.go
@@ -2,7 +2,9 @@ package yqutil
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
+	"io"
 	"os"
 	"strings"
 
@@ -11,6 +13,27 @@ import (
 	logging "gopkg.in/op/go-logging.v1"
 )
 
+// ValidateContent decodes the content yaml, to check it for syntax errors.
+func ValidateContent(content []byte) error {
+	memory := logging.NewMemoryBackend(0)
+	backend := logging.AddModuleLevel(memory)
+	logging.SetBackend(backend)
+	yqlib.InitExpressionParser()
+
+	decoder := yqlib.NewYamlDecoder(yqlib.ConfiguredYamlPreferences)
+
+	reader := bytes.NewReader(content)
+	err := decoder.Init(reader)
+	if err != nil {
+		return err
+	}
+	_, err = decoder.Decode()
+	if errors.Is(err, io.EOF) {
+		return nil
+	}
+	return err
+}
+
 // EvaluateExpression evaluates the yq expression, and returns the modified yaml.
 func EvaluateExpression(expression string, content []byte) ([]byte, error) {
 	logrus.Debugf("Evaluating yq expression: %q", expression)


Test patch:
diff --git a/pkg/limayaml/load_test.go b/pkg/limayaml/load_test.go
index 0a7c09fe..a4a489f1 100644
--- a/pkg/limayaml/load_test.go
+++ b/pkg/limayaml/load_test.go
@@ -28,7 +28,7 @@ provision:
     echo three
 `
 	_, err := Load([]byte(s), "error.yaml")
-	assert.ErrorContains(t, err, "failed to unmarshal YAML")
+	assert.ErrorContains(t, err, "did not find expected key")
 }
 
 func TestLoadDiskString(t *testing.T) {
diff --git a/pkg/yqutil/yqutil_test.go b/pkg/yqutil/yqutil_test.go
index 297a2e2c..e94d9238 100644
--- a/pkg/yqutil/yqutil_test.go
+++ b/pkg/yqutil/yqutil_test.go
@@ -6,6 +6,25 @@ import (
 	"gotest.tools/v3/assert"
 )
 
+func TestValidateContent(t *testing.T) {
+	content := `
+# comment
+foo: bar
+`
+	err := ValidateContent([]byte(content))
+	assert.NilError(t, err)
+}
+
+func TestValidateContentError(t *testing.T) {
+	content := `
+- foo: bar
+  foo
+  bar
+`
+	err := ValidateContent([]byte(content))
+	assert.ErrorContains(t, err, "could not find expected")
+}
+
 func TestEvaluateExpressionSimple(t *testing.T) {
 	expression := `.cpus = 2 | .memory = "2GiB"`
 	content := `