Repository: lima-vm/lima
Pull request #2625 (2024-09-17 09:42:37)

yqutil: fix to preserve line breaks
`yamlfmt` works by replacing line breaks with place holder strings to avoid their loss when using `gopkg.in/yaml.v3`. By replacing line breaks with place holder strings before `yqlib` uses `gopkg.in/yaml.v3`, we can ultimately prevent the loss of line breaks through `yamlfmt`.

https://github.com/lima-vm/lima/pull/2593#issuecomment-2351462571


Patch:
diff --git a/examples/buildkit.yaml b/examples/buildkit.yaml
index 6d27c80a..125c33be 100644
--- a/examples/buildkit.yaml
+++ b/examples/buildkit.yaml
@@ -5,11 +5,11 @@
 # $ export BUILDKIT_HOST=$(limactl list buildkit --format 'unix://{{.Dir}}/sock/buildkitd.sock')
 # $ buildctl debug workers
 message: |
- To run `buildkit` on the host (assumes buildctl is installed), run the following commands:
- -------
- export BUILDKIT_HOST="unix://{{.Dir}}/sock/buildkitd.sock"
- buildctl debug workers
- -------
+  To run `buildkit` on the host (assumes buildctl is installed), run the following commands:
+  -------
+  export BUILDKIT_HOST="unix://{{.Dir}}/sock/buildkitd.sock"
+  buildctl debug workers
+  -------
 images:
 # Try to use release-yyyyMMdd image if available. Note that release-yyyyMMdd will be removed after several months.
 - location: "https://cloud-images.ubuntu.com/releases/24.04/release-20240821/ubuntu-24.04-server-cloudimg-amd64.img"
diff --git a/examples/default.yaml b/examples/default.yaml
index 5b269ff7..d071d889 100644
--- a/examples/default.yaml
+++ b/examples/default.yaml
@@ -264,10 +264,10 @@ containerd:
 # Setting of instructions is supported like this: "qemu64,+ssse3".
 # 🟢 Builtin default: hard-coded arch map with type (see the output of `limactl info | jq .defaultTemplate.cpuType`)
 cpuType:
-  # aarch64: "cortex-a72" # (or "host" when running on aarch64 host)
-  # armv7l: "cortex-a7" # (or "host" when running on armv7l host)
-  # riscv64: "rv64" # (or "host" when running on riscv64 host)
-  # x86_64: "qemu64" # (or "host,-pdpe1gb" when running on x86_64 host)
+#   aarch64: "cortex-a72" # (or "host" when running on aarch64 host)
+#   armv7l: "cortex-a7" # (or "host" when running on armv7l host)
+#   riscv64: "rv64" # (or "host" when running on riscv64 host)
+#   x86_64: "qemu64" # (or "host,-pdpe1gb" when running on x86_64 host)
 
 rosetta:
   # Enable Rosetta for Linux (EXPERIMENTAL; will graduate from experimental in Lima v1.0).
@@ -456,8 +456,8 @@ hostResolver:
   # predefined to specify the gateway address to the host.
   # 🟢 Builtin default: null
   hosts:
-    # guest.name: 127.1.1.1
-    # host.name: host.lima.internal
+  #   guest.name: 127.1.1.1
+  #   host.name: host.lima.internal
 
 # If hostResolver.enabled is false, then the following rules apply for configuring dns:
 # Explicitly set DNS addresses for qemu user-mode networking. By default qemu picks *one*
diff --git a/pkg/executil/opts_others.go b/pkg/instance/ha_cmd_opts_others.go
similarity index 90%
rename from pkg/executil/opts_others.go
rename to pkg/instance/ha_cmd_opts_others.go
index 41c5479a..56ec240b 100644
--- a/pkg/executil/opts_others.go
+++ b/pkg/instance/ha_cmd_opts_others.go
@@ -1,6 +1,6 @@
 //go:build !windows
 
-package executil
+package instance
 
 import (
 	"syscall"
diff --git a/pkg/executil/opts_windows.go b/pkg/instance/ha_cmd_opts_windows.go
similarity index 91%
rename from pkg/executil/opts_windows.go
rename to pkg/instance/ha_cmd_opts_windows.go
index 99f62901..6b7c97ad 100644
--- a/pkg/executil/opts_windows.go
+++ b/pkg/instance/ha_cmd_opts_windows.go
@@ -1,4 +1,4 @@
-package executil
+package instance
 
 import (
 	"syscall"
diff --git a/pkg/instance/start.go b/pkg/instance/start.go
index f3ec3064..00dd9ede 100644
--- a/pkg/instance/start.go
+++ b/pkg/instance/start.go
@@ -17,7 +17,6 @@ import (
 	"github.com/coreos/go-semver/semver"
 	"github.com/lima-vm/lima/pkg/driver"
 	"github.com/lima-vm/lima/pkg/driverutil"
-	"github.com/lima-vm/lima/pkg/executil"
 	"github.com/lima-vm/lima/pkg/osutil"
 	"github.com/lima-vm/lima/pkg/qemu"
 	"github.com/lima-vm/lima/pkg/qemu/entitlementutil"
@@ -192,9 +191,9 @@ func Start(ctx context.Context, inst *store.Instance, launchHostAgentForeground
 	haCmd := exec.CommandContext(ctx, self, args...)
 
 	if launchHostAgentForeground {
-		haCmd.SysProcAttr = executil.ForegroundSysProcAttr
+		haCmd.SysProcAttr = ForegroundSysProcAttr
 	} else {
-		haCmd.SysProcAttr = executil.BackgroundSysProcAttr
+		haCmd.SysProcAttr = BackgroundSysProcAttr
 	}
 
 	haCmd.Stdout = haStdoutW
diff --git a/pkg/networks/usernet/recoincile.go b/pkg/networks/usernet/recoincile.go
index d0026654..8828dc34 100644
--- a/pkg/networks/usernet/recoincile.go
+++ b/pkg/networks/usernet/recoincile.go
@@ -13,7 +13,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/lima-vm/lima/pkg/executil"
 	"github.com/lima-vm/lima/pkg/lockutil"
 	"github.com/lima-vm/lima/pkg/store"
 	"github.com/lima-vm/lima/pkg/store/dirnames"
@@ -82,7 +81,6 @@ func Start(ctx context.Context, name string) error {
 				args = append(args, "--leases", leasesString)
 			}
 			cmd := exec.CommandContext(ctx, self, args...)
-			cmd.SysProcAttr = executil.BackgroundSysProcAttr
 
 			stdoutPath := filepath.Join(usernetDir, fmt.Sprintf("%s.%s.%s.log", "usernet", name, "stdout"))
 			stderrPath := filepath.Join(usernetDir, fmt.Sprintf("%s.%s.%s.log", "usernet", name, "stderr"))
diff --git a/pkg/yqutil/yqutil.go b/pkg/yqutil/yqutil.go
index 139d9fcb..ea7ef9c8 100644
--- a/pkg/yqutil/yqutil.go
+++ b/pkg/yqutil/yqutil.go
@@ -6,6 +6,7 @@ import (
 	"os"
 	"strings"
 
+	"github.com/google/yamlfmt"
 	"github.com/google/yamlfmt/formatters/basic"
 	"github.com/mikefarah/yq/v4/pkg/yqlib"
 	"github.com/sirupsen/logrus"
@@ -15,13 +16,26 @@ import (
 // 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)
+	formatter, err := yamlfmtBasicFormatter()
+	if err != nil {
+		return nil, err
+	}
+	// `ApplyFeatures()` is being called directly before passing content to `yqlib`.
+	// This results in `ApplyFeatures()` being called twice with `FeatureApplyBefore`:
+	// once here and once inside `formatter.Format`.
+	// Currently, calling `ApplyFeatures()` with `FeatureApplyBefore` twice is not an issue,
+	// but future changes to `yamlfmt` might cause problems if it is called twice.
+	contentModified, err := formatter.Features.ApplyFeatures(content, yamlfmt.FeatureApplyBefore)
+	if err != nil {
+		return nil, err
+	}
 	tmpYAMLFile, err := os.CreateTemp("", "lima-yq-*.yaml")
 	if err != nil {
 		return nil, err
 	}
 	tmpYAMLPath := tmpYAMLFile.Name()
 	defer os.RemoveAll(tmpYAMLPath)
-	_, err = tmpYAMLFile.Write(content)
+	_, err = tmpYAMLFile.Write(contentModified)
 	if err != nil {
 		tmpYAMLFile.Close()
 		return nil, err
@@ -70,7 +84,7 @@ func EvaluateExpression(expression string, content []byte) ([]byte, error) {
 		return nil, err
 	}
 
-	return yamlfmt(out.Bytes())
+	return formatter.Format(out.Bytes())
 }
 
 func Join(yqExprs []string) string {
@@ -80,17 +94,23 @@ func Join(yqExprs []string) string {
 	return strings.Join(yqExprs, " | ")
 }
 
-func yamlfmt(content []byte) ([]byte, error) {
+func yamlfmtBasicFormatter() (*basic.BasicFormatter, error) {
 	factory := basic.BasicFormatterFactory{}
 	config := map[string]interface{}{
-		"indentless_arrays":  true,
-		"line_ending":        "lf", // prefer LF even on Windows
-		"pad_line_comments":  2,
-		"retain_line_breaks": true, // does not affect to the output because yq removes empty lines before formatting
+		"indentless_arrays":         true,
+		"line_ending":               "lf", // prefer LF even on Windows
+		"pad_line_comments":         2,
+		"retain_line_breaks":        true,
+		"retain_line_breaks_single": false,
 	}
+
 	formatter, err := factory.NewFormatter(config)
 	if err != nil {
 		return nil, err
 	}
-	return formatter.Format(content)
+	basicFormatter, ok := formatter.(*basic.BasicFormatter)
+	if !ok {
+		return nil, fmt.Errorf("unexpected formatter type: %T", formatter)
+	}
+	return basicFormatter, nil
 }


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f503fd79..7d012d73 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -90,6 +90,10 @@ jobs:
       run: make
     - name: Install
       run: sudo make install
+    - name: Verify templates match `limactl edit` format
+      run: |
+        find examples -name '*.yaml' -exec limactl edit --set 'del(.nothing)' {} \;
+        git diff-index --exit-code HEAD
     - name: Uninstall
       run: sudo make uninstall
 
diff --git a/pkg/yqutil/yqutil_test.go b/pkg/yqutil/yqutil_test.go
index fcf1260e..287c51c6 100644
--- a/pkg/yqutil/yqutil_test.go
+++ b/pkg/yqutil/yqutil_test.go
@@ -19,6 +19,7 @@ memory: null
 	expected := `
 # CPUs
 cpus: 2
+
 # Memory size
 memory: 2GiB
 `