diff --git a/pkg/limayaml/validate.go b/pkg/limayaml/validate.go index e09f56b3a1b..5451466ba7a 100644 --- a/pkg/limayaml/validate.go +++ b/pkg/limayaml/validate.go @@ -396,6 +396,23 @@ func Validate(y *LimaYAML, warn bool) error { warnExperimental(y) } + if y.Rosetta.Enabled != nil && *y.Rosetta.Enabled { + if *y.VMType != VZ { + return fmt.Errorf("field `rosetta.enabled` can only be enabled for VMType %q; got %q", VZ, *y.VMType) + } + if !IsNativeArch(AARCH64) { + return fmt.Errorf("field `rosetta.enabled` can only be enabled on aarch64; got %q", *y.Arch) + } + } + + if y.NestedVirtualization != nil && *y.NestedVirtualization && *y.VMType != VZ { + return fmt.Errorf("field `nestedVirtualization` can only be enabled for VMType %q; got %q", VZ, *y.VMType) + } + + if err := validateMountType(y); err != nil { + return err + } + // Validate Param settings // Names must start with a letter, followed by any number of letters, digits, or underscores validParamName := regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]*$`) @@ -432,6 +449,30 @@ func validateFileObject(f File, fieldName string) error { return errs } +func validateMountType(y *LimaYAML) error { + validMountTypes := map[string]bool{ + REVSSHFS: true, + NINEP: true, + VIRTIOFS: true, + WSLMount: true, + } + + if !validMountTypes[*y.MountType] { + return fmt.Errorf("field `mountType` must be: %q, %q, %q, %q; got %q", REVSSHFS, NINEP, VIRTIOFS, WSLMount, *y.MountType) + } + + // On Windows, only WSL and reverse-sshfs mount types are valid + if runtime.GOOS == "windows" && *y.MountType != WSLMount && *y.MountType != REVSSHFS { + return fmt.Errorf("field `mountType` on Windows must be %q or %q; got %q", WSLMount, REVSSHFS, *y.MountType) + } + + if *y.MountType == VIRTIOFS && runtime.GOOS == "darwin" && *y.VMType != VZ { + return fmt.Errorf("field `mountType` %q on macOS requires vmType %q; got %q", *y.MountType, VZ, *y.VMType) + } + + return nil +} + func validateNetwork(y *LimaYAML) error { var errs error interfaceName := make(map[string]int) @@ -490,6 +531,9 @@ func validateNetwork(y *LimaYAML) error { errs = errors.Join(errs, fmt.Errorf("field `%s.macAddress` must be a 48 bit (6 bytes) MAC address; actual length of %q is %d bytes", field, nw.MACAddress, len(hw))) } } + if nw.VZNAT != nil && *nw.VZNAT && *y.VMType != VZ { + return fmt.Errorf("field `%s.vzNAT` requires vmType %q; got %q", field, VZ, *y.VMType) + } // FillDefault() will make sure that nw.Interface is not the empty string if len(nw.Interface) >= 16 { errs = errors.Join(errs, fmt.Errorf("field `%s.interface` must be less than 16 bytes, but is %d bytes: %q", field, len(nw.Interface), nw.Interface)) diff --git a/pkg/limayaml/validate_test.go b/pkg/limayaml/validate_test.go index 5c9d696a0de..c8717e54772 100644 --- a/pkg/limayaml/validate_test.go +++ b/pkg/limayaml/validate_test.go @@ -5,6 +5,7 @@ package limayaml import ( "errors" + "runtime" "testing" "gotest.tools/v3/assert" @@ -388,3 +389,168 @@ func TestValidateAgainstLatestConfig(t *testing.T) { }) } } + +func TestValidateRosetta(t *testing.T) { + if runtime.GOOS != "darwin" { + t.Skip("Skipping Rosetta validation test on non-macOS platform") + } + + images := `images: [{"location": "/"}]` + + nilData := `` + y, err := Load([]byte(nilData+"\n"+images), "lima.yaml") + assert.NilError(t, err) + + err = Validate(y, false) + assert.NilError(t, err) + + invalidRosetta := `rosetta: + enabled: true +vmType: "qemu" +` + images + y, err = Load([]byte(invalidRosetta), "lima.yaml") + assert.NilError(t, err) + + err = Validate(y, false) + if IsNativeArch(AARCH64) { + assert.Error(t, err, "field `rosetta.enabled` can only be enabled for VMType \"vz\"; got \"qemu\"") + } else { + assert.NilError(t, err) + } + + validRosetta := `rosetta: + enabled: true +vmType: "vz" +` + images + y, err = Load([]byte(validRosetta), "lima.yaml") + assert.NilError(t, err) + + err = Validate(y, false) + assert.NilError(t, err) + + rosettaDisabled := `rosetta: + enabled: false +vmType: "qemu" +` + images + y, err = Load([]byte(rosettaDisabled), "lima.yaml") + assert.NilError(t, err) + + err = Validate(y, false) + assert.NilError(t, err) +} + +func TestValidateNestedVirtualization(t *testing.T) { + if runtime.GOOS != "darwin" { + t.Skip("Skipping nested virtualization validation test on non-macOS platform") + } + + images := `images: [{"location": "/"}]` + + validYAML := ` +nestedVirtualization: true +vmType: vz +` + images + + y, err := Load([]byte(validYAML), "lima.yaml") + assert.NilError(t, err) + + err = Validate(y, false) + assert.NilError(t, err) + + invalidYAML := ` +nestedVirtualization: true +vmType: qemu +` + images + + y, err = Load([]byte(invalidYAML), "lima.yaml") + assert.NilError(t, err) + + err = Validate(y, false) + assert.Error(t, err, "field `nestedVirtualization` can only be enabled for VMType \"vz\"; got \"qemu\"") +} + +func TestValidateMountTypeOS(t *testing.T) { + images := `images: [{"location": "/"}]` + + nilMountConf := `` + y, err := Load([]byte(nilMountConf+"\n"+images), "lima.yaml") + assert.NilError(t, err) + + err = Validate(y, false) + assert.NilError(t, err) + + inValidMountTypeLinux := ` +mountType: "random" +` + y, err = Load([]byte(inValidMountTypeLinux+"\n"+images), "lima.yaml") + assert.NilError(t, err) + + err = Validate(y, true) + assert.Error(t, err, "field `mountType` must be: \"reverse-sshfs\", \"9p\", \"virtiofs\", \"wsl2\"; got \"random\"") + + // Skip macOS-specific tests on non-macOS platforms + if runtime.GOOS != "darwin" { + return + } + + validMountTypeLinux := ` +mountType: "virtiofs" +` + y, err = Load([]byte(validMountTypeLinux+"\n"+images), "lima.yaml") + assert.NilError(t, err) + + err = Validate(y, true) + if IsNativeArch(AARCH64) { + assert.Error(t, err, "field `mountType` \"virtiofs\" on macOS requires vmType \"vz\"; got \"qemu\"") + } else { + assert.NilError(t, err) + } + + validMountTypeMac := ` +mountType: "virtiofs" +vmType: "vz" +` + y, err = Load([]byte(validMountTypeMac+"\n"+images), "lima.yaml") + assert.NilError(t, err) + + err = Validate(y, false) + assert.NilError(t, err) + + invalidMountTypeMac := ` +mountType: "virtiofs" +vmType: "qemu" +` + y, err = Load([]byte(invalidMountTypeMac+"\n"+images), "lima.yaml") + assert.NilError(t, err) + + err = Validate(y, false) + assert.Error(t, err, "field `mountType` \"virtiofs\" on macOS requires vmType \"vz\"; got \"qemu\"") +} + +func TestValidateWindowsMountType(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("Skipping Windows-specific mount type validation test on non-Windows platform") + } + + images := `images: [{"location": "/"}]` + + invalidMountTypes := []string{"9p", "virtiofs"} + for _, mountType := range invalidMountTypes { + invalidMountTypeWindows := `mountType: "` + mountType + `"` + y, err := Load([]byte(invalidMountTypeWindows+"\n"+images), "lima.yaml") + assert.NilError(t, err) + + err = Validate(y, false) + assert.Error(t, err, `field `+"`mountType`"+` on Windows must be "wsl2" or "reverse-sshfs"; got "`+mountType+`"`) + } + + validMountTypes := []string{"wsl2", "reverse-sshfs"} + for _, mountType := range validMountTypes { + validMountTypeWindows := `mountType: "` + mountType + `"` + y, err := Load([]byte(validMountTypeWindows+"\n"+images), "lima.yaml") + assert.NilError(t, err) + + err = Validate(y, false) + assert.NilError(t, err) + } +}