diff --git a/cmd/limactl/clone.go b/cmd/limactl/clone.go index 4aa226c159b..f796b58ed8c 100644 --- a/cmd/limactl/clone.go +++ b/cmd/limactl/clone.go @@ -12,11 +12,12 @@ import ( "github.com/spf13/cobra" "github.com/lima-vm/lima/v2/cmd/limactl/editflags" + "github.com/lima-vm/lima/v2/pkg/driverutil" "github.com/lima-vm/lima/v2/pkg/instance" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/limayaml" networks "github.com/lima-vm/lima/v2/pkg/networks/reconcile" "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" "github.com/lima-vm/lima/v2/pkg/yqutil" ) @@ -79,6 +80,9 @@ func cloneAction(cmd *cobra.Command, args []string) error { if err != nil { return err } + if err := driverutil.ResolveVMType(y, filePath); err != nil { + return fmt.Errorf("failed to accept config for %q: %w", filePath, err) + } if err := limayaml.Validate(y, true); err != nil { return saveRejectedYAML(yBytes, err) } diff --git a/cmd/limactl/copy.go b/cmd/limactl/copy.go index 6204d158969..07e8198d223 100644 --- a/cmd/limactl/copy.go +++ b/cmd/limactl/copy.go @@ -17,6 +17,7 @@ import ( "github.com/spf13/cobra" "github.com/lima-vm/lima/v2/pkg/ioutilx" + "github.com/lima-vm/lima/v2/pkg/limatype" "github.com/lima-vm/lima/v2/pkg/sshutil" "github.com/lima-vm/lima/v2/pkg/store" ) @@ -62,7 +63,7 @@ func copyAction(cmd *cobra.Command, args []string) error { if err != nil { return err } - instances := make(map[string]*store.Instance) + instances := make(map[string]*limatype.Instance) scpFlags := []string{} scpArgs := []string{} debug, err := cmd.Flags().GetBool("debug") @@ -109,7 +110,7 @@ func copyAction(cmd *cobra.Command, args []string) error { } return err } - if inst.Status == store.StatusStopped { + if inst.Status == limatype.StatusStopped { return fmt.Errorf("instance %q is stopped, run `limactl start %s` to start the instance", instName, instName) } if legacySSH { diff --git a/cmd/limactl/disk.go b/cmd/limactl/disk.go index 7b6423d9dd7..6cc159d7ab6 100644 --- a/cmd/limactl/disk.go +++ b/cmd/limactl/disk.go @@ -19,8 +19,9 @@ import ( "github.com/spf13/cobra" "github.com/lima-vm/lima/v2/pkg/imgutil/proxyimgutil" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" ) func newDiskCommand() *cobra.Command { @@ -241,7 +242,7 @@ func diskDeleteAction(cmd *cobra.Command, args []string) error { if err != nil { return err } - var instances []*store.Instance + var instances []*limatype.Instance for _, instName := range instNames { inst, err := store.Inspect(instName) if err != nil { @@ -341,7 +342,7 @@ func diskUnlockAction(_ *cobra.Command, args []string) error { diskName, disk.Instance, inst.Errors) continue } - if inst.Status == store.StatusRunning { + if inst.Status == limatype.StatusRunning { logrus.Warnf("Cannot unlock disk %q used by running instance %q", diskName, disk.Instance) continue } @@ -398,7 +399,7 @@ func diskResizeAction(cmd *cobra.Command, args []string) error { if disk.Instance != "" { inst, err := store.Inspect(disk.Instance) if err == nil { - if inst.Status == store.StatusRunning { + if inst.Status == limatype.StatusRunning { return fmt.Errorf("cannot resize disk %q used by running instance %q. Please stop the VM instance", diskName, disk.Instance) } } diff --git a/cmd/limactl/edit.go b/cmd/limactl/edit.go index 83455f6e10e..0410ad851b8 100644 --- a/cmd/limactl/edit.go +++ b/cmd/limactl/edit.go @@ -14,12 +14,15 @@ import ( "github.com/spf13/cobra" "github.com/lima-vm/lima/v2/cmd/limactl/editflags" + "github.com/lima-vm/lima/v2/pkg/driverutil" "github.com/lima-vm/lima/v2/pkg/editutil" "github.com/lima-vm/lima/v2/pkg/instance" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/limayaml" networks "github.com/lima-vm/lima/v2/pkg/networks/reconcile" "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" "github.com/lima-vm/lima/v2/pkg/uiutil" "github.com/lima-vm/lima/v2/pkg/yqutil" ) @@ -45,12 +48,12 @@ func editAction(cmd *cobra.Command, args []string) error { var filePath string var err error - var inst *store.Instance + var inst *limatype.Instance if arg == "" { arg = DefaultInstanceName } - if err := store.ValidateInstName(arg); err == nil { + if err := dirnames.ValidateInstName(arg); err == nil { inst, err = store.Inspect(arg) if err != nil { if errors.Is(err, os.ErrNotExist) { @@ -58,7 +61,7 @@ func editAction(cmd *cobra.Command, args []string) error { } return err } - if inst.Status == store.StatusRunning { + if inst.Status == limatype.StatusRunning { return errors.New("cannot edit a running instance") } filePath = filepath.Join(inst.Dir, filenames.LimaYAML) @@ -117,6 +120,9 @@ func editAction(cmd *cobra.Command, args []string) error { if err != nil { return err } + if err := driverutil.ResolveVMType(y, filePath); err != nil { + return fmt.Errorf("failed to accept config for %q: %w", filePath, err) + } if err := limayaml.Validate(y, true); err != nil { return saveRejectedYAML(yBytes, err) } diff --git a/cmd/limactl/factory-reset.go b/cmd/limactl/factory-reset.go index a3c4faab825..9044c089df8 100644 --- a/cmd/limactl/factory-reset.go +++ b/cmd/limactl/factory-reset.go @@ -14,8 +14,8 @@ import ( "github.com/lima-vm/lima/v2/pkg/cidata" "github.com/lima-vm/lima/v2/pkg/instance" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" ) func newFactoryResetCommand() *cobra.Command { diff --git a/cmd/limactl/genschema.go b/cmd/limactl/genschema.go index 0739023eab9..d69d74cc169 100644 --- a/cmd/limactl/genschema.go +++ b/cmd/limactl/genschema.go @@ -15,7 +15,7 @@ import ( orderedmap "github.com/wk8/go-ordered-map/v2" "github.com/lima-vm/lima/v2/pkg/jsonschemautil" - "github.com/lima-vm/lima/v2/pkg/limayaml" + "github.com/lima-vm/lima/v2/pkg/limatype" ) func newGenSchemaCommand() *cobra.Command { @@ -52,7 +52,7 @@ func genschemaAction(cmd *cobra.Command, args []string) error { return err } - schema := jsonschema.Reflect(&limayaml.LimaYAML{}) + schema := jsonschema.Reflect(&limatype.LimaYAML{}) // allow Disk to be either string (name) or object (struct) schema.Definitions["Disk"].Type = "" // was: "object" schema.Definitions["Disk"].OneOf = []*jsonschema.Schema{ @@ -72,10 +72,10 @@ func genschemaAction(cmd *cobra.Command, args []string) error { {Type: "object"}, } properties := schema.Definitions["LimaYAML"].Properties - getProp(properties, "os").Enum = toAny(limayaml.OSTypes) - getProp(properties, "arch").Enum = toAny(limayaml.ArchTypes) - getProp(properties, "mountType").Enum = toAny(limayaml.MountTypes) - getProp(properties, "vmType").Enum = toAny(limayaml.VMTypes) + getProp(properties, "os").Enum = toAny(limatype.OSTypes) + getProp(properties, "arch").Enum = toAny(limatype.ArchTypes) + getProp(properties, "mountType").Enum = toAny(limatype.MountTypes) + getProp(properties, "vmType").Enum = toAny(limatype.VMTypes) j, err := json.MarshalIndent(schema, "", " ") if err != nil { return err diff --git a/cmd/limactl/list.go b/cmd/limactl/list.go index da4aa10fe1d..968c65d5d8d 100644 --- a/cmd/limactl/list.go +++ b/cmd/limactl/list.go @@ -16,6 +16,7 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/lima-vm/lima/v2/pkg/limatype" "github.com/lima-vm/lima/v2/pkg/store" ) @@ -171,7 +172,7 @@ func listAction(cmd *cobra.Command, args []string) error { } // get the state and config for all the requested instances - var instances []*store.Instance + var instances []*limatype.Instance for _, instanceName := range instanceNames { instance, err := store.Inspect(instanceName) if err != nil { diff --git a/cmd/limactl/main.go b/cmd/limactl/main.go index 6ee01a0347e..e54072f07da 100644 --- a/cmd/limactl/main.go +++ b/cmd/limactl/main.go @@ -18,8 +18,8 @@ import ( "github.com/lima-vm/lima/v2/pkg/debugutil" "github.com/lima-vm/lima/v2/pkg/driver/external/server" "github.com/lima-vm/lima/v2/pkg/fsutil" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" "github.com/lima-vm/lima/v2/pkg/osutil" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" "github.com/lima-vm/lima/v2/pkg/version" ) diff --git a/cmd/limactl/prune.go b/cmd/limactl/prune.go index 8fa2e7ddc93..c0183849f6f 100644 --- a/cmd/limactl/prune.go +++ b/cmd/limactl/prune.go @@ -4,6 +4,7 @@ package main import ( + "fmt" "maps" "os" @@ -11,6 +12,8 @@ import ( "github.com/spf13/cobra" "github.com/lima-vm/lima/v2/pkg/downloader" + "github.com/lima-vm/lima/v2/pkg/driverutil" + "github.com/lima-vm/lima/v2/pkg/limatype" "github.com/lima-vm/lima/v2/pkg/limayaml" "github.com/lima-vm/lima/v2/pkg/store" "github.com/lima-vm/lima/v2/pkg/templatestore" @@ -62,8 +65,8 @@ func pruneAction(cmd *cobra.Command, _ []string) error { return nil } -func knownLocations() (map[string]limayaml.File, error) { - locations := make(map[string]limayaml.File) +func knownLocations() (map[string]limatype.File, error) { + locations := make(map[string]limatype.File) // Collect locations from instances instances, err := store.Instances() @@ -92,13 +95,16 @@ func knownLocations() (map[string]limayaml.File, error) { if err != nil { return nil, err } + if err := driverutil.ResolveVMType(y, t.Name); err != nil { + return nil, fmt.Errorf("failed to accept config for %q: %w", t.Name, err) + } maps.Copy(locations, locationsFromLimaYAML(y)) } return locations, nil } -func locationsFromLimaYAML(y *limayaml.LimaYAML) map[string]limayaml.File { - locations := make(map[string]limayaml.File) +func locationsFromLimaYAML(y *limatype.LimaYAML) map[string]limatype.File { + locations := make(map[string]limatype.File) for _, f := range y.Images { locations[downloader.CacheKey(f.Location)] = f.File if f.Kernel != nil { diff --git a/cmd/limactl/shell.go b/cmd/limactl/shell.go index a49fe072aa9..6bdafafd7e8 100644 --- a/cmd/limactl/shell.go +++ b/cmd/limactl/shell.go @@ -21,7 +21,7 @@ import ( "github.com/lima-vm/lima/v2/pkg/instance" "github.com/lima-vm/lima/v2/pkg/ioutilx" - "github.com/lima-vm/lima/v2/pkg/limayaml" + "github.com/lima-vm/lima/v2/pkg/limatype" networks "github.com/lima-vm/lima/v2/pkg/networks/reconcile" "github.com/lima-vm/lima/v2/pkg/sshutil" "github.com/lima-vm/lima/v2/pkg/store" @@ -82,7 +82,7 @@ func shellAction(cmd *cobra.Command, args []string) error { } return err } - if inst.Status == store.StatusStopped { + if inst.Status == limatype.StatusStopped { startNow, err := askWhetherToStart() if err != nil { return err @@ -140,7 +140,7 @@ func shellAction(cmd *cobra.Command, args []string) error { if workDir != "" { changeDirCmd = fmt.Sprintf("cd %s || exit 1", shellescape.Quote(workDir)) // FIXME: check whether y.Mounts contains the home, not just len > 0 - } else if len(inst.Config.Mounts) > 0 || inst.VMType == limayaml.WSL2 { + } else if len(inst.Config.Mounts) > 0 || inst.VMType == limatype.WSL2 { hostCurrentDir, err := os.Getwd() if err == nil && runtime.GOOS == "windows" { hostCurrentDir, err = mountDirFromWindowsDir(inst, hostCurrentDir) @@ -245,8 +245,8 @@ func shellAction(cmd *cobra.Command, args []string) error { return sshCmd.Run() } -func mountDirFromWindowsDir(inst *store.Instance, dir string) (string, error) { - if inst.VMType == limayaml.WSL2 { +func mountDirFromWindowsDir(inst *limatype.Instance, dir string) (string, error) { + if inst.VMType == limatype.WSL2 { distroName := "lima-" + inst.Name return ioutilx.WindowsSubsystemPathForLinux(dir, distroName) } diff --git a/cmd/limactl/show-ssh.go b/cmd/limactl/show-ssh.go index eac462ae1f7..a2c6de26af7 100644 --- a/cmd/limactl/show-ssh.go +++ b/cmd/limactl/show-ssh.go @@ -13,10 +13,10 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/sshutil" "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" - "github.com/lima-vm/lima/v2/pkg/store/filenames" ) const showSSHExample = ` diff --git a/cmd/limactl/start.go b/cmd/limactl/start.go index 990bec31028..f1f4f1521ea 100644 --- a/cmd/limactl/start.go +++ b/cmd/limactl/start.go @@ -16,14 +16,17 @@ import ( "github.com/spf13/cobra" "github.com/lima-vm/lima/v2/cmd/limactl/editflags" + "github.com/lima-vm/lima/v2/pkg/driverutil" "github.com/lima-vm/lima/v2/pkg/editutil" "github.com/lima-vm/lima/v2/pkg/instance" "github.com/lima-vm/lima/v2/pkg/limatmpl" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/limayaml" networks "github.com/lima-vm/lima/v2/pkg/networks/reconcile" "github.com/lima-vm/lima/v2/pkg/registry" "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" "github.com/lima-vm/lima/v2/pkg/templatestore" "github.com/lima-vm/lima/v2/pkg/uiutil" "github.com/lima-vm/lima/v2/pkg/yqutil" @@ -102,7 +105,7 @@ See the examples in 'limactl create --help'. return startCommand } -func loadOrCreateInstance(cmd *cobra.Command, args []string, createOnly bool) (*store.Instance, error) { +func loadOrCreateInstance(cmd *cobra.Command, args []string, createOnly bool) (*limatype.Instance, error) { var arg string // can be empty if len(args) > 0 { arg = args[0] @@ -121,7 +124,7 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string, createOnly bool) (* return nil, err } if name != "" { - err := store.ValidateInstName(name) + err := dirnames.ValidateInstName(name) if err != nil { return nil, err } @@ -156,7 +159,7 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string, createOnly bool) (* tty = false } var tmpl *limatmpl.Template - if err := store.ValidateInstName(arg); arg == "" || err == nil { + if err := dirnames.ValidateInstName(arg); arg == "" || err == nil { tmpl = &limatmpl.Template{Name: name} if arg == "" { if name == "" { @@ -211,7 +214,7 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string, createOnly bool) (* if _, err := store.Inspect(tmpl.Name); err == nil { return nil, fmt.Errorf("instance %q already exists", tmpl.Name) } - } else if err := store.ValidateInstName(tmpl.Name); err != nil { + } else if err := dirnames.ValidateInstName(tmpl.Name); err != nil { return nil, err } } @@ -240,7 +243,7 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string, createOnly bool) (* return instance.Create(cmd.Context(), tmpl.Name, tmpl.Bytes, saveBrokenYAML) } -func applyYQExpressionToExistingInstance(inst *store.Instance, yq string) (*store.Instance, error) { +func applyYQExpressionToExistingInstance(inst *limatype.Instance, yq string) (*limatype.Instance, error) { if strings.TrimSpace(yq) == "" { return inst, nil } @@ -258,6 +261,9 @@ func applyYQExpressionToExistingInstance(inst *store.Instance, yq string) (*stor if err != nil { return nil, err } + if err := driverutil.ResolveVMType(y, filePath); err != nil { + return nil, fmt.Errorf("failed to accept config for %q: %w", filePath, err) + } if err := limayaml.Validate(y, true); err != nil { rejectedYAML := "lima.REJECTED.yaml" if writeErr := os.WriteFile(rejectedYAML, yBytes, 0o644); writeErr != nil { @@ -461,15 +467,15 @@ func startAction(cmd *cobra.Command, args []string) error { return fmt.Errorf("errors inspecting instance: %+v", inst.Errors) } switch inst.Status { - case store.StatusRunning: + case limatype.StatusRunning: logrus.Infof("The instance %q is already running. Run `%s` to open the shell.", inst.Name, instance.LimactlShellCmd(inst.Name)) // Not an error return nil - case store.StatusStopped: + case limatype.StatusStopped: // NOP default: - logrus.Warnf("expected status %q, got %q", store.StatusStopped, inst.Status) + logrus.Warnf("expected status %q, got %q", limatype.StatusStopped, inst.Status) } ctx := cmd.Context() err = networks.Reconcile(ctx, inst.Name) diff --git a/cmd/limactl/template.go b/cmd/limactl/template.go index b05a3f11cd2..a386848afd0 100644 --- a/cmd/limactl/template.go +++ b/cmd/limactl/template.go @@ -12,9 +12,10 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/lima-vm/lima/v2/pkg/driverutil" "github.com/lima-vm/lima/v2/pkg/limatmpl" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" "github.com/lima-vm/lima/v2/pkg/limayaml" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" "github.com/lima-vm/lima/v2/pkg/yqutil" ) @@ -86,6 +87,9 @@ func fillDefaults(tmpl *limatmpl.Template) error { if err == nil { tmpl.Bytes, err = limayaml.Marshal(tmpl.Config, false) } + if err := driverutil.ResolveVMType(tmpl.Config, filePath); err != nil { + return fmt.Errorf("failed to accept config for %q: %w", filePath, err) + } return err } @@ -242,6 +246,9 @@ func templateValidateAction(cmd *cobra.Command, args []string) error { if err != nil { return err } + if err := driverutil.ResolveVMType(y, filePath); err != nil { + return fmt.Errorf("failed to accept config for %q: %w", filePath, err) + } if err := limayaml.Validate(y, false); err != nil { return fmt.Errorf("failed to validate YAML file %q: %w", arg, err) } diff --git a/cmd/limactl/tunnel.go b/cmd/limactl/tunnel.go index 189e8f61b8f..e9ada065290 100644 --- a/cmd/limactl/tunnel.go +++ b/cmd/limactl/tunnel.go @@ -15,6 +15,7 @@ import ( "github.com/spf13/cobra" "github.com/lima-vm/lima/v2/pkg/freeport" + "github.com/lima-vm/lima/v2/pkg/limatype" "github.com/lima-vm/lima/v2/pkg/sshutil" "github.com/lima-vm/lima/v2/pkg/store" ) @@ -71,7 +72,7 @@ func tunnelAction(cmd *cobra.Command, args []string) error { } return err } - if inst.Status == store.StatusStopped { + if inst.Status == limatype.StatusStopped { return fmt.Errorf("instance %q is stopped, run `limactl start %s` to start the instance", instName, instName) } diff --git a/pkg/cidata/cidata.go b/pkg/cidata/cidata.go index 0bf39d3f9a1..ab05824ecbf 100644 --- a/pkg/cidata/cidata.go +++ b/pkg/cidata/cidata.go @@ -25,13 +25,14 @@ import ( "github.com/lima-vm/lima/v2/pkg/debugutil" "github.com/lima-vm/lima/v2/pkg/instance/hostname" "github.com/lima-vm/lima/v2/pkg/iso9660util" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/limayaml" "github.com/lima-vm/lima/v2/pkg/localpathutil" "github.com/lima-vm/lima/v2/pkg/networks" "github.com/lima-vm/lima/v2/pkg/networks/usernet" "github.com/lima-vm/lima/v2/pkg/osutil" "github.com/lima-vm/lima/v2/pkg/sshutil" - "github.com/lima-vm/lima/v2/pkg/store/filenames" ) var netLookupIP = func(host string) []net.IP { @@ -115,7 +116,7 @@ func setupEnv(instConfigEnv map[string]string, propagateProxyEnv bool, slirpGate return env, nil } -func templateArgs(bootScripts bool, instDir, name string, instConfig *limayaml.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort, vsockPort int, virtioPort string) (*TemplateArgs, error) { +func templateArgs(bootScripts bool, instDir, name string, instConfig *limatype.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort, vsockPort int, virtioPort string) (*TemplateArgs, error) { if err := limayaml.Validate(instConfig, false); err != nil { return nil, err } @@ -163,7 +164,7 @@ func templateArgs(bootScripts bool, instDir, name string, instConfig *limayaml.L return nil, err } args.SlirpGateway = usernet.GatewayIP(subnet) - if *instConfig.VMType == limayaml.VZ { + if *instConfig.VMType == limatype.VZ { args.SlirpDNS = usernet.GatewayIP(subnet) } else { args.SlirpDNS = usernet.DNSIP(subnet) @@ -187,11 +188,11 @@ func templateArgs(bootScripts bool, instDir, name string, instConfig *limayaml.L var fstype string switch *instConfig.MountType { - case limayaml.REVSSHFS: + case limatype.REVSSHFS: fstype = "sshfs" - case limayaml.NINEP: + case limatype.NINEP: fstype = "9p" - case limayaml.VIRTIOFS: + case limatype.VIRTIOFS: fstype = "virtiofs" } hostHome, err := localpathutil.Expand("~") @@ -227,11 +228,11 @@ func templateArgs(bootScripts bool, instDir, name string, instConfig *limayaml.L } switch *instConfig.MountType { - case limayaml.REVSSHFS: + case limatype.REVSSHFS: args.MountType = "reverse-sshfs" - case limayaml.NINEP: + case limatype.NINEP: args.MountType = "9p" - case limayaml.VIRTIOFS: + case limatype.VIRTIOFS: args.MountType = "virtiofs" } @@ -271,7 +272,7 @@ func templateArgs(bootScripts bool, instDir, name string, instConfig *limayaml.L for _, addr := range instConfig.DNS { args.DNSAddresses = append(args.DNSAddresses, addr.String()) } - case firstUsernetIndex != -1 || *instConfig.VMType == limayaml.VZ: + case firstUsernetIndex != -1 || *instConfig.VMType == limatype.VZ: args.DNSAddresses = append(args.DNSAddresses, args.SlirpDNS) case *instConfig.HostResolver.Enabled: args.UDPDNSLocalPort = udpDNSLocalPort @@ -315,10 +316,10 @@ func templateArgs(bootScripts bool, instDir, name string, instConfig *limayaml.L args.BootCmds = getBootCmds(instConfig.Provision) for i, f := range instConfig.Provision { - if f.Mode == limayaml.ProvisionModeDependency && *f.SkipDefaultDependencyResolution { + if f.Mode == limatype.ProvisionModeDependency && *f.SkipDefaultDependencyResolution { args.SkipDefaultDependencyResolution = true } - if f.Mode == limayaml.ProvisionModeData { + if f.Mode == limatype.ProvisionModeData { args.DataFiles = append(args.DataFiles, DataFile{ FileName: fmt.Sprintf("%08d", i), Overwrite: strconv.FormatBool(*f.Overwrite), @@ -332,7 +333,7 @@ func templateArgs(bootScripts bool, instDir, name string, instConfig *limayaml.L return &args, nil } -func GenerateCloudConfig(instDir, name string, instConfig *limayaml.LimaYAML) error { +func GenerateCloudConfig(instDir, name string, instConfig *limatype.LimaYAML) error { args, err := templateArgs(false, instDir, name, instConfig, 0, 0, 0, "") if err != nil { return err @@ -355,7 +356,7 @@ func GenerateCloudConfig(instDir, name string, instConfig *limayaml.LimaYAML) er return os.WriteFile(filepath.Join(instDir, filenames.CloudConfig), config, 0o444) } -func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort int, guestAgentBinary, nerdctlArchive string, vsockPort int, virtioPort string) error { +func GenerateISO9660(instDir, name string, instConfig *limatype.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort int, guestAgentBinary, nerdctlArchive string, vsockPort int, virtioPort string) error { args, err := templateArgs(true, instDir, name, instConfig, udpDNSLocalPort, tcpDNSLocalPort, vsockPort, virtioPort) if err != nil { return err @@ -372,19 +373,19 @@ func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNS for i, f := range instConfig.Provision { switch f.Mode { - case limayaml.ProvisionModeSystem, limayaml.ProvisionModeUser, limayaml.ProvisionModeDependency: + case limatype.ProvisionModeSystem, limatype.ProvisionModeUser, limatype.ProvisionModeDependency: layout = append(layout, iso9660util.Entry{ Path: fmt.Sprintf("provision.%s/%08d", f.Mode, i), Reader: strings.NewReader(f.Script), }) - case limayaml.ProvisionModeData: + case limatype.ProvisionModeData: layout = append(layout, iso9660util.Entry{ Path: fmt.Sprintf("provision.%s/%08d", f.Mode, i), Reader: strings.NewReader(*f.Content), }) - case limayaml.ProvisionModeBoot: + case limatype.ProvisionModeBoot: continue - case limayaml.ProvisionModeAnsible: + case limatype.ProvisionModeAnsible: continue default: return fmt.Errorf("unknown provision mode %q", f.Mode) @@ -432,7 +433,7 @@ func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNS }) } - if args.VMType == limayaml.WSL2 { + if args.VMType == limatype.WSL2 { layout = append(layout, iso9660util.Entry{ Path: "ssh_authorized_keys", Reader: strings.NewReader(strings.Join(args.SSHPubKeys, "\n")), @@ -455,10 +456,10 @@ func getCert(content string) Cert { return Cert{Lines: lines} } -func getBootCmds(p []limayaml.Provision) []BootCmds { +func getBootCmds(p []limatype.Provision) []BootCmds { var bootCmds []BootCmds for _, f := range p { - if f.Mode == limayaml.ProvisionModeBoot { + if f.Mode == limatype.ProvisionModeBoot { lines := []string{} for _, line := range strings.Split(f.Script, "\n") { if line == "" { diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go index 7527039c50d..c91a92c3355 100644 --- a/pkg/driver/driver.go +++ b/pkg/driver/driver.go @@ -7,7 +7,7 @@ import ( "context" "net" - "github.com/lima-vm/lima/v2/pkg/store" + "github.com/lima-vm/lima/v2/pkg/limatype" ) // Lifecycle defines basic lifecycle operations. @@ -15,13 +15,13 @@ type Lifecycle interface { // Validate returns error if the current driver isn't support for given config Validate() error - // Initialize is called on creating the instance for initialization. + // Create is called on creating the instance for the first time. // (e.g., creating "vz-identifier" file) // - // Initialize MUST return nil when it is called against an existing instance. + // Create MUST return nil when it is called against an existing instance. // - // Initialize does not create the disks. - Initialize(_ context.Context) error + // Create does not create the disks. + Create(_ context.Context) error // CreateDisk returns error if the current driver fails in creating disk CreateDisk(_ context.Context) error @@ -34,6 +34,9 @@ type Lifecycle interface { // Stop will terminate the running vm instance. // It returns error if there are any errors during Stop Stop(_ context.Context) error + + Delete(_ context.Context) error + InspectStatus(_ context.Context, instName string) string } // GUI defines GUI-related operations. @@ -54,12 +57,6 @@ type SnapshotManager interface { ListSnapshots(ctx context.Context) (string, error) } -// Registration defines operations for registering and unregistering the driver instance. -type Registration interface { - Register(ctx context.Context) error - Unregister(ctx context.Context) error -} - // GuestAgent defines operations for the guest agent. type GuestAgent interface { // ForwardGuestAgent returns if the guest agent sock needs forwarding by host agent. @@ -74,13 +71,15 @@ type Driver interface { Lifecycle GUI SnapshotManager - Registration GuestAgent Info() Info // SetConfig sets the configuration for the instance. - Configure(inst *store.Instance) *ConfiguredDriver + Configure(inst *limatype.Instance) *ConfiguredDriver + + AcceptConfig(cfg *limatype.LimaYAML, filepath string) error + FillConfig(cfg *limatype.LimaYAML, filePath string) error } type ConfiguredDriver struct { diff --git a/pkg/driver/external/client/methods.go b/pkg/driver/external/client/methods.go index 120d2b3f088..640c3d80712 100644 --- a/pkg/driver/external/client/methods.go +++ b/pkg/driver/external/client/methods.go @@ -14,7 +14,7 @@ import ( "github.com/lima-vm/lima/v2/pkg/driver" pb "github.com/lima-vm/lima/v2/pkg/driver/external" - "github.com/lima-vm/lima/v2/pkg/store" + "github.com/lima-vm/lima/v2/pkg/limatype" ) func (d *DriverClient) Validate() error { @@ -33,7 +33,7 @@ func (d *DriverClient) Validate() error { return nil } -func (d *DriverClient) Initialize(ctx context.Context) error { +func (d *DriverClient) Create(ctx context.Context) error { d.logger.Debug("Initializing driver instance") _, err := d.DriverSvc.Initialize(ctx, &emptypb.Empty{}) @@ -281,7 +281,7 @@ func (d *DriverClient) Info() driver.Info { return info } -func (d *DriverClient) Configure(inst *store.Instance) *driver.ConfiguredDriver { +func (d *DriverClient) Configure(inst *limatype.Instance) *driver.ConfiguredDriver { d.logger.Debugf("Setting config for instance %s with SSH local port %d", inst.Name, inst.SSHLocalPort) instJSON, err := inst.MarshalJSON() @@ -306,3 +306,20 @@ func (d *DriverClient) Configure(inst *store.Instance) *driver.ConfiguredDriver Driver: d, } } + +func (d *DriverClient) Delete(ctx context.Context) error { + return errors.New("Delete not implemented in DriverClient") +} + +func (d *DriverClient) AcceptConfig(cfg *limatype.LimaYAML, filepath string) error { + return errors.New("AcceptConfig not implemented in DriverClient") +} + +func (d *DriverClient) FillConfig(cfg *limatype.LimaYAML, filepath string) error { + return errors.New("FillConfig not implemented in DriverClient") +} + +// TODO: Implement InspectStatus in DriverClient +func (d *DriverClient) InspectStatus(_ context.Context, _ string) string { + return "" +} diff --git a/pkg/driver/external/server/methods.go b/pkg/driver/external/server/methods.go index 44c190fe925..fcc6b81b4e9 100644 --- a/pkg/driver/external/server/methods.go +++ b/pkg/driver/external/server/methods.go @@ -16,8 +16,8 @@ import ( "github.com/lima-vm/lima/v2/pkg/bicopy" pb "github.com/lima-vm/lima/v2/pkg/driver/external" - "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" ) func (s *DriverServer) Start(_ *emptypb.Empty, stream pb.Driver_StartServer) error { @@ -55,7 +55,7 @@ func (s *DriverServer) Start(_ *emptypb.Empty, stream pb.Driver_StartServer) err func (s *DriverServer) SetConfig(_ context.Context, req *pb.SetConfigRequest) (*emptypb.Empty, error) { s.logger.Debugf("Received SetConfig request") - var inst store.Instance + var inst limatype.Instance if err := inst.UnmarshalJSON(req.InstanceConfigJson); err != nil { s.logger.Errorf("Failed to unmarshal InstanceConfigJson: %v", err) @@ -127,9 +127,10 @@ func (s *DriverServer) Validate(_ context.Context, empty *emptypb.Empty) (*empty return empty, nil } +// TODO: Update the proto file func (s *DriverServer) Initialize(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { s.logger.Debug("Received Initialize request") - err := s.driver.Initialize(ctx) + err := s.driver.Create(ctx) if err != nil { s.logger.Errorf("Initialization failed: %v", err) return empty, err @@ -237,27 +238,28 @@ func (s *DriverServer) ListSnapshots(ctx context.Context, _ *emptypb.Empty) (*pb return &pb.ListSnapshotsResponse{Snapshots: snapshots}, nil } -func (s *DriverServer) Register(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { - s.logger.Debug("Received Register request") - err := s.driver.Register(ctx) - if err != nil { - s.logger.Errorf("Register failed: %v", err) - return empty, err - } - s.logger.Debug("Register succeeded") - return empty, nil -} - -func (s *DriverServer) Unregister(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { - s.logger.Debug("Received Unregister request") - err := s.driver.Unregister(ctx) - if err != nil { - s.logger.Errorf("Unregister failed: %v", err) - return empty, err - } - s.logger.Debug("Unregister succeeded") - return empty, nil -} +// TODO: Delete these methods and update the proto file accordingly +// func (s *DriverServer) Register(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { +// s.logger.Debug("Received Register request") +// err := s.driver.Register(ctx) +// if err != nil { +// s.logger.Errorf("Register failed: %v", err) +// return empty, err +// } +// s.logger.Debug("Register succeeded") +// return empty, nil +// } + +// func (s *DriverServer) Unregister(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { +// s.logger.Debug("Received Unregister request") +// err := s.driver.Unregister(ctx) +// if err != nil { +// s.logger.Errorf("Unregister failed: %v", err) +// return empty, err +// } +// s.logger.Debug("Unregister succeeded") +// return empty, nil +// } func (s *DriverServer) ForwardGuestAgent(_ context.Context, _ *emptypb.Empty) (*pb.ForwardGuestAgentResponse, error) { s.logger.Debug("Received ForwardGuestAgent request") diff --git a/pkg/driver/external/server/server.go b/pkg/driver/external/server/server.go index b0c4140a4e5..dfed1c0bf99 100644 --- a/pkg/driver/external/server/server.go +++ b/pkg/driver/external/server/server.go @@ -24,9 +24,9 @@ import ( "github.com/lima-vm/lima/v2/pkg/driver" pb "github.com/lima-vm/lima/v2/pkg/driver/external" "github.com/lima-vm/lima/v2/pkg/driver/external/client" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/registry" - "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" ) type DriverServer struct { @@ -114,7 +114,7 @@ func Start(extDriver *registry.ExternalDriver, instName string) error { return fmt.Errorf("failed to create stdout pipe for external driver: %w", err) } - instanceDir, err := store.InstanceDir(extDriver.InstanceName) + instanceDir, err := dirnames.InstanceDir(extDriver.InstanceName) if err != nil { cancel() return fmt.Errorf("failed to determine instance directory: %w", err) diff --git a/pkg/driver/qemu/qemu.go b/pkg/driver/qemu/qemu.go index 7c8004e0338..627aa0319a4 100644 --- a/pkg/driver/qemu/qemu.go +++ b/pkg/driver/qemu/qemu.go @@ -30,19 +30,20 @@ import ( "github.com/lima-vm/lima/v2/pkg/fileutils" "github.com/lima-vm/lima/v2/pkg/iso9660util" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/limayaml" "github.com/lima-vm/lima/v2/pkg/networks" "github.com/lima-vm/lima/v2/pkg/networks/usernet" "github.com/lima-vm/lima/v2/pkg/osutil" "github.com/lima-vm/lima/v2/pkg/qemuimgutil" "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" ) type Config struct { Name string InstanceDir string - LimaYAML *limayaml.LimaYAML + LimaYAML *limatype.LimaYAML SSHLocalPort int SSHAddress string VirtioGA bool @@ -384,8 +385,8 @@ func adjustMemBytesDarwinARM64HVF(memBytes int64, accel string) int64 { } // qemuMachine returns string to use for -machine. -func qemuMachine(arch limayaml.Arch) string { - if arch == limayaml.X8664 { +func qemuMachine(arch limatype.Arch) string { + if arch == limatype.X8664 { return "q35" } return "virt" @@ -404,7 +405,7 @@ func audioDevice() string { return "oss" } -func defaultCPUType() limayaml.CPUType { +func defaultCPUType() limatype.CPUType { // x86_64 + TCG + max was previously unstable until 2021. // https://bugzilla.redhat.com/show_bug.cgi?id=1999700 // https://bugs.launchpad.net/qemu/+bug/1748296 @@ -417,13 +418,13 @@ func defaultCPUType() limayaml.CPUType { // TODO: remove this if "max" works with the latest qemu defaultX8664 = "qemu64" } - cpuType := map[limayaml.Arch]string{ - limayaml.AARCH64: "max", - limayaml.ARMV7L: "max", - limayaml.X8664: defaultX8664, - limayaml.PPC64LE: "max", - limayaml.RISCV64: "max", - limayaml.S390X: "max", + cpuType := map[limatype.Arch]string{ + limatype.AARCH64: "max", + limatype.ARMV7L: "max", + limatype.X8664: defaultX8664, + limatype.PPC64LE: "max", + limatype.RISCV64: "max", + limatype.S390X: "max", } for arch := range cpuType { if limayaml.IsNativeArch(arch) && limayaml.IsAccelOS() { @@ -431,7 +432,7 @@ func defaultCPUType() limayaml.CPUType { cpuType[arch] = "host" } } - if arch == limayaml.X8664 && runtime.GOOS == "darwin" { + if arch == limatype.X8664 && runtime.GOOS == "darwin" { // disable AVX-512, since it requires trapping instruction faults in guest // Enterprise Linux requires either v2 (SSE4) or v3 (AVX2), but not yet v4. cpuType[arch] += ",-avx512vl" @@ -445,11 +446,11 @@ func defaultCPUType() limayaml.CPUType { return cpuType } -func resolveCPUType(y *limayaml.LimaYAML) string { +func resolveCPUType(y *limatype.LimaYAML) string { cpuType := defaultCPUType() var overrideCPUType bool for k, v := range y.VMOpts.QEMU.CPUType { - if !slices.Contains(limayaml.ArchTypes, *y.Arch) { + if !slices.Contains(limatype.ArchTypes, *y.Arch) { logrus.Warnf("field `vmOpts.qemu.cpuType` uses unsupported arch %q", k) continue } @@ -508,7 +509,7 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er memBytes = adjustMemBytesDarwinARM64HVF(memBytes, accel) args = appendArgsIfNoConflict(args, "-m", strconv.Itoa(int(memBytes>>20))) - if *y.MountType == limayaml.VIRTIOFS { + if *y.MountType == limatype.VIRTIOFS { args = appendArgsIfNoConflict(args, "-object", fmt.Sprintf("memory-backend-file,id=virtiofs-shm,size=%s,mem-path=/dev/shm,share=on", strconv.Itoa(int(memBytes)))) args = appendArgsIfNoConflict(args, "-numa", "node,memdev=virtiofs-shm") @@ -532,7 +533,7 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er // Machine switch *y.Arch { - case limayaml.X8664: + case limatype.X8664: switch accel { case "tcg": // use q35 machine with vmware io port disabled. @@ -551,10 +552,10 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er default: args = appendArgsIfNoConflict(args, "-machine", "q35,accel="+accel) } - case limayaml.AARCH64: + case limatype.AARCH64: machine := "virt,accel=" + accel args = appendArgsIfNoConflict(args, "-machine", machine) - case limayaml.RISCV64: + case limatype.RISCV64: // https://github.com/tianocore/edk2/blob/edk2-stable202408/OvmfPkg/RiscVVirt/README.md#test // > Note: the `acpi=off` machine property is specified because Linux guest // > support for ACPI (that is, the ACPI consumer side) is a work in progress. @@ -562,13 +563,13 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er // > yourself. machine := "virt,acpi=off,accel=" + accel args = appendArgsIfNoConflict(args, "-machine", machine) - case limayaml.ARMV7L: + case limatype.ARMV7L: machine := "virt,accel=" + accel args = appendArgsIfNoConflict(args, "-machine", machine) - case limayaml.PPC64LE: + case limatype.PPC64LE: machine := "pseries,accel=" + accel args = appendArgsIfNoConflict(args, "-machine", machine) - case limayaml.S390X: + case limatype.S390X: machine := "s390-ccw-virtio,accel=" + accel args = appendArgsIfNoConflict(args, "-machine", machine) } @@ -579,11 +580,11 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er // Firmware legacyBIOS := *y.Firmware.LegacyBIOS - if legacyBIOS && *y.Arch != limayaml.X8664 && *y.Arch != limayaml.ARMV7L { + if legacyBIOS && *y.Arch != limatype.X8664 && *y.Arch != limatype.ARMV7L { logrus.Warnf("field `firmware.legacyBIOS` is not supported for architecture %q, ignoring", *y.Arch) legacyBIOS = false } - noFirmware := *y.Arch == limayaml.PPC64LE || *y.Arch == limayaml.S390X || legacyBIOS + noFirmware := *y.Arch == limatype.PPC64LE || *y.Arch == limatype.S390X || legacyBIOS if !noFirmware { var firmware string firmwareInBios := runtime.GOOS == "windows" @@ -595,7 +596,7 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er firmwareInBios = b } } - firmwareInBios = firmwareInBios && *y.Arch == limayaml.X8664 + firmwareInBios = firmwareInBios && *y.Arch == limatype.X8664 downloadedFirmware := filepath.Join(cfg.InstanceDir, filenames.QemuEfiCodeFD) firmwareWithVars := filepath.Join(cfg.InstanceDir, filenames.QemuEfiFullFD) if firmwareInBios { @@ -608,7 +609,7 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er loop: for _, f := range y.Firmware.Images { switch f.VMType { - case "", limayaml.QEMU: + case "", limatype.QEMU: if f.Arch == *y.Arch { if _, err = fileutils.DownloadFile(ctx, downloadedFirmware, f.File, true, "UEFI code "+f.Location, *y.Arch); err != nil { logrus.WithError(err).Warnf("failed to download %q", f.Location) @@ -768,7 +769,7 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er args = append(args, "-netdev", fmt.Sprintf("socket,id=net0,fd={{ fd_connect %q }}", qemuSock)) } virtioNet := "virtio-net-pci" - if *y.Arch == limayaml.S390X { + if *y.Arch == limatype.S390X { // virtio-net-pci does not work on EL, while it works on Ubuntu // https://github.com/lima-vm/lima/pull/3319/files#r1986388345 virtioNet = "virtio-net-ccw" @@ -853,7 +854,7 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er switch *y.Arch { // FIXME: use virtio-gpu on all the architectures - case limayaml.X8664, limayaml.RISCV64: + case limatype.X8664, limatype.RISCV64: args = append(args, "-device", "virtio-vga") default: args = append(args, "-device", "virtio-gpu") @@ -883,7 +884,7 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er // On ARM, the default serial is ttyAMA0, this PCI serial is ttyS0. // https://gitlab.com/qemu-project/qemu/-/issues/1801#note_1494720586 switch *y.Arch { - case limayaml.AARCH64, limayaml.ARMV7L: + case limatype.AARCH64, limatype.ARMV7L: serialpSock := filepath.Join(cfg.InstanceDir, filenames.SerialPCISock) if err := os.RemoveAll(serialpSock); err != nil { return "", nil, err @@ -910,7 +911,7 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er args = append(args, "-chardev", fmt.Sprintf("socket,id=%s,path=%s,server=on,wait=off,logfile=%s", serialvChardev, serialvSock, serialvLog)) // max_ports=1 is required for https://github.com/lima-vm/lima/issues/1689 https://github.com/lima-vm/lima/issues/1691 serialvMaxPorts := 1 - if *y.Arch == limayaml.S390X { + if *y.Arch == limatype.S390X { serialvMaxPorts++ // needed to avoid `virtio-serial-bus: Out-of-range port id specified, max. allowed: 0` } args = append(args, "-device", fmt.Sprintf("virtio-serial-pci,id=virtio-serial0,max_ports=%d", serialvMaxPorts)) @@ -918,7 +919,7 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er // We also want to enable vsock here, but QEMU does not support vsock for macOS hosts - if *y.MountType == limayaml.NINEP || *y.MountType == limayaml.VIRTIOFS { + if *y.MountType == limatype.NINEP || *y.MountType == limatype.VIRTIOFS { for i, f := range y.Mounts { tag := fmt.Sprintf("mount%d", i) if err := os.MkdirAll(f.Location, 0o755); err != nil { @@ -926,7 +927,7 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er } switch *y.MountType { - case limayaml.NINEP: + case limatype.NINEP: options := "local" options += fmt.Sprintf(",mount_tag=%s", tag) options += fmt.Sprintf(",path=%s", f.Location) @@ -935,7 +936,7 @@ func Cmdline(ctx context.Context, cfg Config) (exe string, args []string, err er options += ",readonly" } args = append(args, "-virtfs", options) - case limayaml.VIRTIOFS: + case limatype.VIRTIOFS: // Note that read-only mode is not supported on the QEMU/virtiofsd side yet: // https://gitlab.com/virtio-fs/virtiofsd/-/issues/97 chardev := fmt.Sprintf("char-virtiofs-%d", i) @@ -1065,11 +1066,11 @@ func VirtiofsdCmdline(cfg Config, mountIndex int) ([]string, error) { } // qemuArch returns the arch string used by qemu. -func qemuArch(arch limayaml.Arch) string { +func qemuArch(arch limatype.Arch) string { switch arch { - case limayaml.ARMV7L: + case limatype.ARMV7L: return "arm" - case limayaml.PPC64LE: + case limatype.PPC64LE: return "ppc64" default: return arch @@ -1077,14 +1078,14 @@ func qemuArch(arch limayaml.Arch) string { } // qemuEdk2 returns the arch string used by `/usr/local/share/qemu/edk2-*-code.fd`. -func qemuEdk2Arch(arch limayaml.Arch) string { - if arch == limayaml.RISCV64 { +func qemuEdk2Arch(arch limatype.Arch) string { + if arch == limatype.RISCV64 { return "riscv" } return qemuArch(arch) } -func Exe(arch limayaml.Arch) (exe string, args []string, err error) { +func Exe(arch limatype.Arch) (exe string, args []string, err error) { exeBase := "qemu-system-" + qemuArch(arch) envK := "QEMU_SYSTEM_" + strings.ToUpper(qemuArch(arch)) if envV := os.Getenv(envK); envV != "" { @@ -1104,7 +1105,7 @@ func Exe(arch limayaml.Arch) (exe string, args []string, err error) { return exe, args, nil } -func Accel(arch limayaml.Arch) string { +func Accel(arch limatype.Arch) string { if limayaml.IsNativeArch(arch) { switch runtime.GOOS { case "darwin": @@ -1147,9 +1148,9 @@ func getQemuVersion(qemuExe string) (*semver.Version, error) { return parseQemuVersion(stdout.String()) } -func getFirmware(qemuExe string, arch limayaml.Arch) (string, error) { +func getFirmware(qemuExe string, arch limatype.Arch) (string, error) { switch arch { - case limayaml.X8664, limayaml.AARCH64, limayaml.ARMV7L, limayaml.RISCV64: + case limatype.X8664, limatype.AARCH64, limatype.ARMV7L, limatype.RISCV64: default: return "", fmt.Errorf("unexpected architecture: %q", arch) } @@ -1172,7 +1173,7 @@ func getFirmware(qemuExe string, arch limayaml.Arch) (string, error) { } switch arch { - case limayaml.X8664: + case limatype.X8664: // Archlinux package "edk2-ovmf" // @see: https://archlinux.org/packages/extra/any/edk2-ovmf/files candidates = append(candidates, "/usr/share/edk2/x64/OVMF_CODE.4m.fd") @@ -1183,7 +1184,7 @@ func getFirmware(qemuExe string, arch limayaml.Arch) (string, error) { candidates = append(candidates, "/usr/share/edk2/ovmf/OVMF_CODE.fd") // openSUSE package "qemu-ovmf-x86_64" candidates = append(candidates, "/usr/share/qemu/ovmf-x86_64.bin") - case limayaml.AARCH64: + case limatype.AARCH64: // Archlinux package "edk2-aarch64" // @see: https://archlinux.org/packages/extra/any/edk2-aarch64/files candidates = append(candidates, "/usr/share/edk2/aarch64/QEMU_CODE.fd") @@ -1192,14 +1193,14 @@ func getFirmware(qemuExe string, arch limayaml.Arch) (string, error) { candidates = append(candidates, "/usr/share/AAVMF/AAVMF_CODE.fd") // Debian package "qemu-efi-aarch64" (unpadded, backwards compatibility) candidates = append(candidates, "/usr/share/qemu-efi-aarch64/QEMU_EFI.fd") - case limayaml.ARMV7L: + case limatype.ARMV7L: // Archlinux package "edk2-arm" // @see: https://archlinux.org/packages/extra/any/edk2-arm/files candidates = append(candidates, "/usr/share/edk2/arm/QEMU_CODE.fd") // Debian package "qemu-efi-arm" // Fedora package "edk2-arm" candidates = append(candidates, "/usr/share/AAVMF/AAVMF32_CODE.fd") - case limayaml.RISCV64: + case limatype.RISCV64: // Debian package "qemu-efi-riscv64" candidates = append(candidates, "/usr/share/qemu-efi-riscv64/RISCV_VIRT_CODE.fd") // Fedora package "edk2-riscv64" @@ -1214,17 +1215,17 @@ func getFirmware(qemuExe string, arch limayaml.Arch) (string, error) { } } - if arch == limayaml.X8664 { + if arch == limatype.X8664 { return "", fmt.Errorf("could not find firmware for %q (hint: try setting `firmware.legacyBIOS` to `true`)", arch) } qemuArch := strings.TrimPrefix(filepath.Base(qemuExe), "qemu-system-") return "", fmt.Errorf("could not find firmware for %q (hint: try copying the \"edk-%s-code.fd\" firmware to $HOME/.local/share/qemu/)", arch, qemuArch) } -func getFirmwareVars(qemuExe string, arch limayaml.Arch) (string, error) { +func getFirmwareVars(qemuExe string, arch limatype.Arch) (string, error) { var targetArch string switch arch { - case limayaml.X8664: + case limatype.X8664: targetArch = "i386" // vars are unified between i386 and x86_64 and normally only former is bundled default: return "", fmt.Errorf("unexpected architecture: %q", arch) diff --git a/pkg/driver/qemu/qemu_driver.go b/pkg/driver/qemu/qemu_driver.go index 7ab3947cfb8..8a203a9c6b5 100644 --- a/pkg/driver/qemu/qemu_driver.go +++ b/pkg/driver/qemu/qemu_driver.go @@ -16,6 +16,7 @@ import ( "os/exec" "path/filepath" "runtime" + "slices" "strconv" "strings" "text/template" @@ -29,15 +30,17 @@ import ( "github.com/lima-vm/lima/v2/pkg/driver" "github.com/lima-vm/lima/v2/pkg/driver/qemu/entitlementutil" "github.com/lima-vm/lima/v2/pkg/executil" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/limayaml" "github.com/lima-vm/lima/v2/pkg/networks/usernet" "github.com/lima-vm/lima/v2/pkg/osutil" - "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" + "github.com/lima-vm/lima/v2/pkg/ptr" + "github.com/lima-vm/lima/v2/pkg/version/versionutil" ) type LimaQemuDriver struct { - Instance *store.Instance + Instance *limatype.Instance SSHLocalPort int vSockPort int virtioPort string @@ -64,7 +67,7 @@ func New() *LimaQemuDriver { } } -func (l *LimaQemuDriver) Configure(inst *store.Instance) *driver.ConfiguredDriver { +func (l *LimaQemuDriver) Configure(inst *limatype.Instance) *driver.ConfiguredDriver { l.Instance = inst l.SSHLocalPort = inst.SSHLocalPort @@ -79,11 +82,122 @@ func (l *LimaQemuDriver) Validate() error { return err } } + if err := l.validateMountType(); err != nil { + return err + } + + return nil +} + +// Helper method for mount type validation +func (l *LimaQemuDriver) validateMountType() error { + if l.Instance == nil || l.Instance.Config == nil { + return fmt.Errorf("instance configuration is not set") + } - if *l.Instance.Config.MountType == limayaml.VIRTIOFS && runtime.GOOS != "linux" { + cfg := l.Instance.Config + + if cfg.MountType != nil && *cfg.MountType == limatype.VIRTIOFS && runtime.GOOS != "linux" { return fmt.Errorf("field `mountType` must be %q or %q for QEMU driver on non-Linux, got %q", - limayaml.REVSSHFS, limayaml.NINEP, *l.Instance.Config.MountType) + limatype.REVSSHFS, limatype.NINEP, *cfg.MountType) + } + if cfg.MountTypesUnsupported != nil && cfg.MountType != nil && slices.Contains(cfg.MountTypesUnsupported, *cfg.MountType) { + return fmt.Errorf("mount type %q is explicitly unsupported", *cfg.MountType) } + if runtime.GOOS == "windows" && cfg.MountType != nil && *cfg.MountType == limatype.NINEP { + return fmt.Errorf("mount type %q is not supported on Windows", limatype.NINEP) + } + + return nil +} + +func (l *LimaQemuDriver) FillConfig(cfg *limatype.LimaYAML, filePath string) error { + if cfg.VMType == nil { + cfg.VMType = ptr.Of(limatype.QEMU) + } + + instDir := filepath.Dir(filePath) + + if cfg.Video.VNC.Display == nil || *cfg.Video.VNC.Display == "" { + cfg.Video.VNC.Display = ptr.Of("127.0.0.1:0,to=9") + } + + mountTypesUnsupported := make(map[string]struct{}) + for _, f := range cfg.MountTypesUnsupported { + mountTypesUnsupported[f] = struct{}{} + } + + if runtime.GOOS == "windows" { + // QEMU for Windows does not support 9p + mountTypesUnsupported[limatype.NINEP] = struct{}{} + } + + if cfg.MountType == nil || *cfg.MountType == "" || *cfg.MountType == "default" { + cfg.MountType = ptr.Of(limatype.NINEP) + if _, ok := mountTypesUnsupported[limatype.NINEP]; ok { + // Use REVSSHFS if the instance does not support 9p + cfg.MountType = ptr.Of(limatype.REVSSHFS) + } else if limayaml.IsExistingInstanceDir(instDir) && !versionutil.GreaterEqual(limayaml.ExistingLimaVersion(instDir), "1.0.0") { + // Use REVSSHFS if the instance was created with Lima prior to v1.0 + cfg.MountType = ptr.Of(limatype.REVSSHFS) + } + } + + for i := range cfg.Mounts { + mount := &cfg.Mounts[i] + if mount.Virtiofs.QueueSize == nil && *cfg.MountType == limatype.VIRTIOFS { + cfg.Mounts[i].Virtiofs.QueueSize = ptr.Of(limayaml.DefaultVirtiofsQueueSize) + } + } + + if _, ok := mountTypesUnsupported[*cfg.MountType]; ok { + return fmt.Errorf("mount type %q is explicitly unsupported", *cfg.MountType) + } + + return nil +} + +func (l *LimaQemuDriver) AcceptConfig(cfg *limatype.LimaYAML, filePath string) error { + if l.Instance == nil { + l.Instance = &limatype.Instance{} + } + l.Instance.Config = cfg + + if err := l.Validate(); err != nil { + return fmt.Errorf("config not supported by the QEMU driver: %w", err) + } + + if runtime.GOOS == "darwin" { + if cfg.Arch != nil && limayaml.IsNativeArch(*cfg.Arch) { + logrus.Debugf("ResolveVMType: resolved VMType %q (non-native arch=%q is specified in []*LimaYAML{o,y,d})", "qemu", *cfg.Arch) + return nil + } + if limayaml.ResolveArch(cfg.Arch) == limatype.X8664 && cfg.Firmware.LegacyBIOS != nil && *cfg.Firmware.LegacyBIOS { + logrus.Debugf("ResolveVMType: resolved VMType %q (firmware.legacyBIOS is specified in []*LimaYAML{o,y,d} on x86_64)", "qemu") + return nil + } + if cfg.MountType != nil && *cfg.MountType == limatype.NINEP { + logrus.Debugf("ResolveVMType: resolved VMType %q (mountType=%q is specified in []*LimaYAML{o,y,d})", "qemu", limatype.NINEP) + return nil + } + if cfg.Audio.Device != nil { + switch *cfg.Audio.Device { + case "", "none", "default", "vz": + // NOP + default: + logrus.Debugf("ResolveVMType: resolved VMType %q (audio.device=%q is specified in []*LimaYAML{o,y,d})", "qemu", *cfg.Audio.Device) + return nil + } + } + if cfg.Video.Display != nil { + display := *cfg.Video.Display + if display != "" && display != "none" && display != "default" && display != "vz" { + logrus.Debugf("ResolveVMType: resolved VMType %q (video.display=%q is specified in []*LimaYAML{o,y,d})", "qemu", display) + return nil + } + } + } + return nil } @@ -118,7 +232,7 @@ func (l *LimaQemuDriver) Start(ctx context.Context) (chan error, error) { } var vhostCmds []*exec.Cmd - if *l.Instance.Config.MountType == limayaml.VIRTIOFS { + if *l.Instance.Config.MountType == limatype.VIRTIOFS { vhostExe, err := FindVirtiofsd(qExe) if err != nil { return nil, err @@ -275,7 +389,7 @@ func (l *LimaQemuDriver) checkBinarySignature() error { return err } // The codesign --xml option is only available on macOS Monterey and later - if !macOSProductVersion.LessThan(*semver.New("12.0.0")) { + if !macOSProductVersion.LessThan(*semver.New("12.0.0")) && l.Instance.Arch != "" { qExe, _, err := Exe(l.Instance.Arch) if err != nil { return fmt.Errorf("failed to find the QEMU binary for the architecture %q: %w", l.Instance.Arch, err) @@ -427,7 +541,7 @@ func (l *LimaQemuDriver) DeleteSnapshot(_ context.Context, tag string) error { InstanceDir: l.Instance.Dir, LimaYAML: l.Instance.Config, } - return Del(qCfg, l.Instance.Status == store.StatusRunning, tag) + return Del(qCfg, l.Instance.Status == limatype.StatusRunning, tag) } func (l *LimaQemuDriver) CreateSnapshot(_ context.Context, tag string) error { @@ -436,7 +550,7 @@ func (l *LimaQemuDriver) CreateSnapshot(_ context.Context, tag string) error { InstanceDir: l.Instance.Dir, LimaYAML: l.Instance.Config, } - return Save(qCfg, l.Instance.Status == store.StatusRunning, tag) + return Save(qCfg, l.Instance.Status == limatype.StatusRunning, tag) } func (l *LimaQemuDriver) ApplySnapshot(_ context.Context, tag string) error { @@ -445,7 +559,7 @@ func (l *LimaQemuDriver) ApplySnapshot(_ context.Context, tag string) error { InstanceDir: l.Instance.Dir, LimaYAML: l.Instance.Config, } - return Load(qCfg, l.Instance.Status == store.StatusRunning, tag) + return Load(qCfg, l.Instance.Status == limatype.StatusRunning, tag) } func (l *LimaQemuDriver) ListSnapshots(_ context.Context) (string, error) { @@ -454,7 +568,7 @@ func (l *LimaQemuDriver) ListSnapshots(_ context.Context) (string, error) { InstanceDir: l.Instance.Dir, LimaYAML: l.Instance.Config, } - return List(qCfg, l.Instance.Status == store.StatusRunning) + return List(qCfg, l.Instance.Status == limatype.StatusRunning) } func (l *LimaQemuDriver) GuestAgentConn(ctx context.Context) (net.Conn, string, error) { @@ -527,7 +641,15 @@ func (l *LimaQemuDriver) Info() driver.Info { return info } -func (l *LimaQemuDriver) Initialize(_ context.Context) error { +func (l *LimaQemuDriver) InspectStatus(_ context.Context, instName string) string { + return "" +} + +func (l *LimaQemuDriver) Create(_ context.Context) error { + return nil +} + +func (l *LimaQemuDriver) Delete(ctx context.Context) error { return nil } diff --git a/pkg/driver/vz/disk.go b/pkg/driver/vz/disk.go index 138e67dd079..24832a84a37 100644 --- a/pkg/driver/vz/disk.go +++ b/pkg/driver/vz/disk.go @@ -14,11 +14,11 @@ import ( "github.com/lima-vm/lima/v2/pkg/imgutil/proxyimgutil" "github.com/lima-vm/lima/v2/pkg/iso9660util" - "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" ) -func EnsureDisk(_ context.Context, inst *store.Instance) error { +func EnsureDisk(_ context.Context, inst *limatype.Instance) error { diffDisk := filepath.Join(inst.Dir, filenames.DiffDisk) if _, err := os.Stat(diffDisk); err == nil || !errors.Is(err, os.ErrNotExist) { // disk is already ensured diff --git a/pkg/driver/vz/vm_darwin.go b/pkg/driver/vz/vm_darwin.go index 0889c29dc92..daa93eb8192 100644 --- a/pkg/driver/vz/vm_darwin.go +++ b/pkg/driver/vz/vm_darwin.go @@ -27,12 +27,13 @@ import ( "github.com/lima-vm/lima/v2/pkg/imgutil/proxyimgutil" "github.com/lima-vm/lima/v2/pkg/iso9660util" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/limayaml" "github.com/lima-vm/lima/v2/pkg/networks" "github.com/lima-vm/lima/v2/pkg/networks/usernet" "github.com/lima-vm/lima/v2/pkg/osutil" "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" ) // diskImageCachingMode is set to DiskImageCachingModeCached so as to avoid disk corruption on ARM: @@ -51,7 +52,7 @@ type virtualMachineWrapper struct { // Hold all *os.File created via socketpair() so that they won't get garbage collected. f.FD() gets invalid if f gets garbage collected. var vmNetworkFiles = make([]*os.File, 1) -func startVM(ctx context.Context, inst *store.Instance, sshLocalPort int) (*virtualMachineWrapper, chan error, error) { +func startVM(ctx context.Context, inst *limatype.Instance, sshLocalPort int) (*virtualMachineWrapper, chan error, error) { usernetClient, err := startUsernet(ctx, inst) if err != nil { return nil, nil, err @@ -128,7 +129,7 @@ func startVM(ctx context.Context, inst *store.Instance, sshLocalPort int) (*virt return wrapper, errCh, err } -func startUsernet(ctx context.Context, inst *store.Instance) (*usernet.Client, error) { +func startUsernet(ctx context.Context, inst *limatype.Instance) (*usernet.Client, error) { if firstUsernetIndex := limayaml.FirstUsernetIndex(inst.Config); firstUsernetIndex != -1 { nwName := inst.Config.Networks[firstUsernetIndex].Lima return usernet.NewClientByName(nwName), nil @@ -161,7 +162,7 @@ func startUsernet(ctx context.Context, inst *store.Instance) (*usernet.Client, e return usernet.NewClient(endpointSock, subnetIP), err } -func createVM(inst *store.Instance) (*vz.VirtualMachine, error) { +func createVM(inst *limatype.Instance) (*vz.VirtualMachine, error) { vmConfig, err := createInitialConfig(inst) if err != nil { return nil, err @@ -207,7 +208,7 @@ func createVM(inst *store.Instance) (*vz.VirtualMachine, error) { return vz.NewVirtualMachine(vmConfig) } -func createInitialConfig(inst *store.Instance) (*vz.VirtualMachineConfiguration, error) { +func createInitialConfig(inst *limatype.Instance) (*vz.VirtualMachineConfiguration, error) { bootLoader, err := bootLoader(inst) if err != nil { return nil, err @@ -229,7 +230,7 @@ func createInitialConfig(inst *store.Instance) (*vz.VirtualMachineConfiguration, return vmConfig, nil } -func attachPlatformConfig(inst *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error { +func attachPlatformConfig(inst *limatype.Instance, vmConfig *vz.VirtualMachineConfiguration) error { machineIdentifier, err := getMachineIdentifier(inst) if err != nil { return err @@ -264,7 +265,7 @@ func attachPlatformConfig(inst *store.Instance, vmConfig *vz.VirtualMachineConfi return nil } -func attachSerialPort(inst *store.Instance, config *vz.VirtualMachineConfiguration) error { +func attachSerialPort(inst *limatype.Instance, config *vz.VirtualMachineConfiguration) error { path := filepath.Join(inst.Dir, filenames.SerialVirtioLog) serialPortAttachment, err := vz.NewFileSerialPortAttachment(path, false) if err != nil { @@ -302,7 +303,7 @@ func newVirtioNetworkDeviceConfiguration(attachment vz.NetworkDeviceAttachment, return networkConfig, nil } -func attachNetwork(inst *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error { +func attachNetwork(inst *limatype.Instance, vmConfig *vz.VirtualMachineConfiguration) error { var configurations []*vz.VirtioNetworkDeviceConfiguration // Configure default usernetwork with limayaml.MACAddress(inst.Dir) for eth0 interface @@ -434,7 +435,7 @@ func validateDiskFormat(diskPath string) error { return nil } -func attachDisks(inst *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error { +func attachDisks(inst *limatype.Instance, vmConfig *vz.VirtualMachineConfiguration) error { baseDiskPath := filepath.Join(inst.Dir, filenames.BaseDisk) diffDiskPath := filepath.Join(inst.Dir, filenames.DiffDisk) ciDataPath := filepath.Join(inst.Dir, filenames.CIDataISO) @@ -523,7 +524,7 @@ func attachDisks(inst *store.Instance, vmConfig *vz.VirtualMachineConfiguration) return nil } -func attachDisplay(inst *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error { +func attachDisplay(inst *limatype.Instance, vmConfig *vz.VirtualMachineConfiguration) error { switch *inst.Config.Video.Display { case "vz", "default": graphicsDeviceConfiguration, err := vz.NewVirtioGraphicsDeviceConfiguration() @@ -547,9 +548,9 @@ func attachDisplay(inst *store.Instance, vmConfig *vz.VirtualMachineConfiguratio } } -func attachFolderMounts(inst *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error { +func attachFolderMounts(inst *limatype.Instance, vmConfig *vz.VirtualMachineConfiguration) error { var mounts []vz.DirectorySharingDeviceConfiguration - if *inst.Config.MountType == limayaml.VIRTIOFS { + if *inst.Config.MountType == limatype.VIRTIOFS { for i, mount := range inst.Config.Mounts { if _, err := os.Stat(mount.Location); errors.Is(err, os.ErrNotExist) { err := os.MkdirAll(mount.Location, 0o750) @@ -593,7 +594,7 @@ func attachFolderMounts(inst *store.Instance, vmConfig *vz.VirtualMachineConfigu return nil } -func attachAudio(inst *store.Instance, config *vz.VirtualMachineConfiguration) error { +func attachAudio(inst *limatype.Instance, config *vz.VirtualMachineConfiguration) error { switch *inst.Config.Audio.Device { case "vz", "default": outputStream, err := vz.NewVirtioSoundDeviceHostOutputStreamConfiguration() @@ -616,7 +617,7 @@ func attachAudio(inst *store.Instance, config *vz.VirtualMachineConfiguration) e } } -func attachOtherDevices(_ *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error { +func attachOtherDevices(_ *limatype.Instance, vmConfig *vz.VirtualMachineConfiguration) error { entropyConfig, err := vz.NewVirtioEntropyDeviceConfiguration() if err != nil { return err @@ -690,7 +691,7 @@ func attachOtherDevices(_ *store.Instance, vmConfig *vz.VirtualMachineConfigurat return nil } -func getMachineIdentifier(inst *store.Instance) (*vz.GenericMachineIdentifier, error) { +func getMachineIdentifier(inst *limatype.Instance) (*vz.GenericMachineIdentifier, error) { identifier := filepath.Join(inst.Dir, filenames.VzIdentifier) // Empty VzIdentifier can be created on cloning an instance. if st, err := os.Stat(identifier); err != nil || (st != nil && st.Size() == 0) { @@ -710,7 +711,7 @@ func getMachineIdentifier(inst *store.Instance) (*vz.GenericMachineIdentifier, e return vz.NewGenericMachineIdentifierWithDataPath(identifier) } -func bootLoader(inst *store.Instance) (vz.BootLoader, error) { +func bootLoader(inst *limatype.Instance) (vz.BootLoader, error) { linuxBootLoader, err := linuxBootLoader(inst) if linuxBootLoader != nil { return linuxBootLoader, nil @@ -726,7 +727,7 @@ func bootLoader(inst *store.Instance) (vz.BootLoader, error) { return vz.NewEFIBootLoader(vz.WithEFIVariableStore(efiVariableStore)) } -func linuxBootLoader(inst *store.Instance) (*vz.LinuxBootLoader, error) { +func linuxBootLoader(inst *limatype.Instance) (*vz.LinuxBootLoader, error) { kernel := filepath.Join(inst.Dir, filenames.Kernel) kernelCmdline := filepath.Join(inst.Dir, filenames.KernelCmdline) initrd := filepath.Join(inst.Dir, filenames.Initrd) @@ -751,7 +752,7 @@ func linuxBootLoader(inst *store.Instance) (*vz.LinuxBootLoader, error) { return vz.NewLinuxBootLoader(kernel, opt...) } -func getEFI(inst *store.Instance) (*vz.EFIVariableStore, error) { +func getEFI(inst *limatype.Instance) (*vz.EFIVariableStore, error) { efi := filepath.Join(inst.Dir, filenames.VzEfi) if _, err := os.Stat(efi); os.IsNotExist(err) { return vz.NewEFIVariableStore(efi, vz.WithCreatingEFIVariableStore()) diff --git a/pkg/driver/vz/vz_driver_darwin.go b/pkg/driver/vz/vz_driver_darwin.go index 7055c3cdbc0..b8bf87f1da5 100644 --- a/pkg/driver/vz/vz_driver_darwin.go +++ b/pkg/driver/vz/vz_driver_darwin.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "net" + "os" "path/filepath" "runtime" "time" @@ -19,10 +20,12 @@ import ( "github.com/sirupsen/logrus" "github.com/lima-vm/lima/v2/pkg/driver" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/limayaml" "github.com/lima-vm/lima/v2/pkg/osutil" + "github.com/lima-vm/lima/v2/pkg/ptr" "github.com/lima-vm/lima/v2/pkg/reflectutil" - "github.com/lima-vm/lima/v2/pkg/store" ) var knownYamlProperties = []string{ @@ -64,12 +67,13 @@ var knownYamlProperties = []string{ "User", "Video", "VMType", + "VMOpts", } const Enabled = true type LimaVzDriver struct { - Instance *store.Instance + Instance *limatype.Instance SSHLocalPort int vSockPort int @@ -87,14 +91,58 @@ func New() *LimaVzDriver { } } -func (l *LimaVzDriver) Configure(inst *store.Instance) *driver.ConfiguredDriver { +func (l *LimaVzDriver) Configure(inst *limatype.Instance) *driver.ConfiguredDriver { l.Instance = inst l.SSHLocalPort = inst.SSHLocalPort + if l.Instance.Config.MountType != nil { + mountTypesUnsupported := make(map[string]struct{}) + for _, f := range l.Instance.Config.MountTypesUnsupported { + mountTypesUnsupported[f] = struct{}{} + } + + if _, ok := mountTypesUnsupported[*l.Instance.Config.MountType]; ok { + // We cannot return an error here, but Validate() will return it. + logrus.Warnf("Unsupported mount type: %q", *l.Instance.Config.MountType) + } + } + return &driver.ConfiguredDriver{ Driver: l, } } +func (l *LimaVzDriver) FillConfig(cfg *limatype.LimaYAML, filePath string) error { + if cfg.VMType == nil { + cfg.VMType = ptr.Of(limatype.VZ) + } + + if cfg.MountType == nil { + cfg.MountType = ptr.Of(limatype.VIRTIOFS) + } + + return nil +} + +func (l *LimaVzDriver) AcceptConfig(cfg *limatype.LimaYAML, filePath string) error { + if dir, basename := filepath.Split(filePath); dir != "" && basename == filenames.LimaYAML && limayaml.IsExistingInstanceDir(dir) { + vzIdentifier := filepath.Join(dir, filenames.VzIdentifier) // since Lima v0.14 + if _, err := os.Lstat(vzIdentifier); !errors.Is(err, os.ErrNotExist) { + logrus.Debugf("ResolveVMType: resolved VMType %q (existing instance, with %q)", "vz", vzIdentifier) + return nil + } + } + + if l.Instance == nil { + l.Instance = &limatype.Instance{} + } + l.Instance.Config = cfg + + if err := l.Validate(); err != nil { + return fmt.Errorf("config not supported by the VZ driver: %w", err) + } + + return nil +} func (l *LimaVzDriver) Validate() error { macOSProductVersion, err := osutil.ProductVersion() @@ -109,21 +157,21 @@ func (l *LimaVzDriver) Validate() error { "Update macOS, or change vmType to \"qemu\" if the VM does not start up. (https://github.com/lima-vm/lima/issues/3334)", *l.Instance.Config.VMType) } - if *l.Instance.Config.MountType == limayaml.NINEP { - return fmt.Errorf("field `mountType` must be %q or %q for VZ driver , got %q", limayaml.REVSSHFS, limayaml.VIRTIOFS, *l.Instance.Config.MountType) + if l.Instance.Config.MountType != nil && *l.Instance.Config.MountType == limatype.NINEP { + return fmt.Errorf("field `mountType` must be %q or %q for VZ driver , got %q", limatype.REVSSHFS, limatype.VIRTIOFS, *l.Instance.Config.MountType) } if *l.Instance.Config.Firmware.LegacyBIOS { logrus.Warnf("vmType %s: ignoring `firmware.legacyBIOS`", *l.Instance.Config.VMType) } for _, f := range l.Instance.Config.Firmware.Images { switch f.VMType { - case "", limayaml.VZ: + case "", limatype.VZ: if f.Arch == *l.Instance.Config.Arch { return errors.New("`firmware.images` configuration is not supported for VZ driver") } } } - if unknown := reflectutil.UnknownNonEmptyFields(l.Instance.Config, knownYamlProperties...); len(unknown) > 0 { + if unknown := reflectutil.UnknownNonEmptyFields(l.Instance.Config, knownYamlProperties...); l.Instance.Config.VMType != nil && len(unknown) > 0 { logrus.Warnf("vmType %s: ignoring %+v", *l.Instance.Config.VMType, unknown) } @@ -175,7 +223,7 @@ func (l *LimaVzDriver) Validate() error { return nil } -func (l *LimaVzDriver) Initialize(_ context.Context) error { +func (l *LimaVzDriver) Create(_ context.Context) error { _, err := getMachineIdentifier(l.Instance) return err } @@ -268,6 +316,14 @@ func (l *LimaVzDriver) Info() driver.Info { return info } +func (l *LimaVzDriver) InspectStatus(_ context.Context, instName string) string { + return "" +} + +func (l *LimaVzDriver) Delete(ctx context.Context) error { + return nil +} + func (l *LimaVzDriver) Register(_ context.Context) error { return nil } diff --git a/pkg/driver/wsl2/fs.go b/pkg/driver/wsl2/fs.go index ddbc0bbde69..b247b29d663 100644 --- a/pkg/driver/wsl2/fs.go +++ b/pkg/driver/wsl2/fs.go @@ -12,12 +12,12 @@ import ( "github.com/sirupsen/logrus" "github.com/lima-vm/lima/v2/pkg/fileutils" - "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" ) // EnsureFs downloads the root fs. -func EnsureFs(ctx context.Context, inst *store.Instance) error { +func EnsureFs(ctx context.Context, inst *limatype.Instance) error { baseDisk := filepath.Join(inst.Dir, filenames.BaseDisk) if _, err := os.Stat(baseDisk); errors.Is(err, os.ErrNotExist) { var ensuredBaseDisk bool diff --git a/pkg/driver/wsl2/vm_windows.go b/pkg/driver/wsl2/vm_windows.go index 7add196d35b..49a7fb8b40b 100644 --- a/pkg/driver/wsl2/vm_windows.go +++ b/pkg/driver/wsl2/vm_windows.go @@ -10,14 +10,15 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "strconv" "strings" "github.com/sirupsen/logrus" "github.com/lima-vm/lima/v2/pkg/executil" - "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/textutil" ) @@ -132,8 +133,8 @@ func provisionVM(ctx context.Context, instanceDir, instanceName, distroName stri for { <-ctx.Done() logrus.Info("Context closed, stopping vm") - if status, err := store.GetWslStatus(instanceName); err == nil && - status == store.StatusRunning { + if status, err := getWslStatus(instanceName); err == nil && + status == limatype.StatusRunning { _ = stopVM(ctx, distroName) } } @@ -176,3 +177,54 @@ func unregisterVM(ctx context.Context, distroName string) error { } return nil } + +func getWslStatus(instName string) (string, error) { + distroName := "lima-" + instName + out, err := executil.RunUTF16leCommand([]string{ + "wsl.exe", + "--list", + "--verbose", + }) + if err != nil { + return "", fmt.Errorf("failed to run `wsl --list --verbose`, err: %w (out=%q)", err, out) + } + + if out == "" { + return limatype.StatusBroken, fmt.Errorf("failed to read instance state for instance %q, try running `wsl --list --verbose` to debug, err: %w", instName, err) + } + + // Check for edge cases first + if strings.Contains(out, "Windows Subsystem for Linux has no installed distributions.") { + if strings.Contains(out, "Wsl/WSL_E_DEFAULT_DISTRO_NOT_FOUND") { + return limatype.StatusBroken, fmt.Errorf( + "failed to read instance state for instance %q because no distro is installed,"+ + "try running `wsl --install -d Ubuntu` and then re-running Lima", instName) + } + return limatype.StatusBroken, fmt.Errorf( + "failed to read instance state for instance %q because there is no WSL kernel installed,"+ + "this usually happens when WSL was installed for another user, but never for your user."+ + "Try running `wsl --install -d Ubuntu` and `wsl --update`, and then re-running Lima", instName) + } + + var instState string + wslListColsRegex := regexp.MustCompile(`\s+`) + // wsl --list --verbose may have different headers depending on localization, just split by line + for _, rows := range strings.Split(strings.ReplaceAll(out, "\r\n", "\n"), "\n") { + cols := wslListColsRegex.Split(strings.TrimSpace(rows), -1) + nameIdx := 0 + // '*' indicates default instance + if cols[0] == "*" { + nameIdx = 1 + } + if cols[nameIdx] == distroName { + instState = cols[nameIdx+1] + break + } + } + + if instState == "" { + return limatype.StatusUninitialized, nil + } + + return instState, nil +} diff --git a/pkg/driver/wsl2/wsl_driver_windows.go b/pkg/driver/wsl2/wsl_driver_windows.go index 821f0b6bb4a..4b111e17af4 100644 --- a/pkg/driver/wsl2/wsl_driver_windows.go +++ b/pkg/driver/wsl2/wsl_driver_windows.go @@ -15,7 +15,9 @@ import ( "github.com/lima-vm/lima/v2/pkg/driver" "github.com/lima-vm/lima/v2/pkg/freeport" + "github.com/lima-vm/lima/v2/pkg/limatype" "github.com/lima-vm/lima/v2/pkg/limayaml" + "github.com/lima-vm/lima/v2/pkg/ptr" "github.com/lima-vm/lima/v2/pkg/reflectutil" "github.com/lima-vm/lima/v2/pkg/store" "github.com/lima-vm/lima/v2/pkg/windows" @@ -47,7 +49,7 @@ var knownYamlProperties = []string{ const Enabled = true type LimaWslDriver struct { - Instance *store.Instance + Instance *limatype.Instance SSHLocalPort int vSockPort int @@ -68,7 +70,7 @@ func New() *LimaWslDriver { } } -func (l *LimaWslDriver) Configure(inst *store.Instance) *driver.ConfiguredDriver { +func (l *LimaWslDriver) Configure(inst *limatype.Instance) *driver.ConfiguredDriver { l.Instance = inst l.SSHLocalPort = inst.SSHLocalPort @@ -77,9 +79,33 @@ func (l *LimaWslDriver) Configure(inst *store.Instance) *driver.ConfiguredDriver } } +func (l *LimaWslDriver) AcceptConfig(cfg *limatype.LimaYAML, filepath string) error { + if l.Instance == nil { + l.Instance = &limatype.Instance{} + } + l.Instance.Config = cfg + + if err := l.Validate(); err != nil { + return fmt.Errorf("config not supported by the WSL2 driver: %w", err) + } + + return nil +} + +func (l *LimaWslDriver) FillConfig(cfg *limatype.LimaYAML, filePath string) error { + if cfg.VMType == nil { + cfg.VMType = ptr.Of(limatype.WSL2) + } + if cfg.MountType == nil { + cfg.MountType = ptr.Of(limatype.WSLMount) + } + + return nil +} + func (l *LimaWslDriver) Validate() error { - if *l.Instance.Config.MountType != limayaml.WSLMount { - return fmt.Errorf("field `mountType` must be %q for WSL2 driver, got %q", limayaml.WSLMount, *l.Instance.Config.MountType) + if l.Instance.Config.MountType != nil && *l.Instance.Config.MountType != limatype.WSLMount { + return fmt.Errorf("field `mountType` must be %q for WSL2 driver, got %q", limatype.WSLMount, *l.Instance.Config.MountType) } // TODO: revise this list for WSL2 if unknown := reflectutil.UnknownNonEmptyFields(l.Instance.Config, knownYamlProperties...); len(unknown) > 0 { @@ -90,48 +116,96 @@ func (l *LimaWslDriver) Validate() error { return fmt.Errorf("unsupported arch: %q", *l.Instance.Config.Arch) } - // TODO: real filetype checks - tarFileRegex := regexp.MustCompile(`.*tar\.*`) - for i, image := range l.Instance.Config.Images { - if unknown := reflectutil.UnknownNonEmptyFields(image, "File"); len(unknown) > 0 { - logrus.Warnf("Ignoring: vmType %s: images[%d]: %+v", *l.Instance.Config.VMType, i, unknown) + if l.Instance.Config.VMType != nil { + if l.Instance.Config.Images != nil && l.Instance.Config.Arch != nil { + // TODO: real filetype checks + tarFileRegex := regexp.MustCompile(`.*tar\.*`) + for i, image := range l.Instance.Config.Images { + if unknown := reflectutil.UnknownNonEmptyFields(image, "File"); len(unknown) > 0 { + logrus.Warnf("Ignoring: vmType %s: images[%d]: %+v", *l.Instance.Config.VMType, i, unknown) + } + match := tarFileRegex.MatchString(image.Location) + if image.Arch == *l.Instance.Config.Arch && !match { + return fmt.Errorf("unsupported image type for vmType: %s, tarball root file system required: %q", *l.Instance.Config.VMType, image.Location) + } + } } - match := tarFileRegex.MatchString(image.Location) - if image.Arch == *l.Instance.Config.Arch && !match { - return fmt.Errorf("unsupported image type for vmType: %s, tarball root file system required: %q", *l.Instance.Config.VMType, image.Location) + + if l.Instance.Config.Mounts != nil { + for i, mount := range l.Instance.Config.Mounts { + if unknown := reflectutil.UnknownNonEmptyFields(mount); len(unknown) > 0 { + logrus.Warnf("Ignoring: vmType %s: mounts[%d]: %+v", *l.Instance.Config.VMType, i, unknown) + } + } } - } - for i, mount := range l.Instance.Config.Mounts { - if unknown := reflectutil.UnknownNonEmptyFields(mount); len(unknown) > 0 { - logrus.Warnf("Ignoring: vmType %s: mounts[%d]: %+v", *l.Instance.Config.VMType, i, unknown) + if l.Instance.Config.Networks != nil { + for i, network := range l.Instance.Config.Networks { + if unknown := reflectutil.UnknownNonEmptyFields(network); len(unknown) > 0 { + logrus.Warnf("Ignoring: vmType %s: networks[%d]: %+v", *l.Instance.Config.VMType, i, unknown) + } + } } + + if l.Instance.Config.Audio.Device != nil { + audioDevice := *l.Instance.Config.Audio.Device + if audioDevice != "" { + logrus.Warnf("Ignoring: vmType %s: `audio.device`: %+v", *l.Instance.Config.VMType, audioDevice) + } + } + } + + return nil +} + +func (l *LimaWslDriver) InspectStatus(ctx context.Context, instName string) string { + status, err := getWslStatus(instName) + if err != nil { + l.Instance.Status = limatype.StatusBroken + l.Instance.Errors = append(l.Instance.Errors, err) + } else { + l.Instance.Status = status } - for i, network := range l.Instance.Config.Networks { - if unknown := reflectutil.UnknownNonEmptyFields(network); len(unknown) > 0 { - logrus.Warnf("Ignoring: vmType %s: networks[%d]: %+v", *l.Instance.Config.VMType, i, unknown) + l.Instance.SSHLocalPort = 22 + + if l.Instance.Status == limatype.StatusRunning { + sshAddr, err := store.GetSSHAddress(instName) + if err == nil { + l.Instance.SSHAddress = sshAddr + } else { + l.Instance.Errors = append(l.Instance.Errors, err) } } - audioDevice := *l.Instance.Config.Audio.Device - if audioDevice != "" { - logrus.Warnf("Ignoring: vmType %s: `audio.device`: %+v", *l.Instance.Config.VMType, audioDevice) + return l.Instance.Status +} + +func (l *LimaWslDriver) Delete(ctx context.Context) error { + distroName := "lima-" + l.Instance.Name + status, err := getWslStatus(l.Instance.Name) + if err != nil { + return err + } + switch status { + case limatype.StatusRunning, limatype.StatusStopped, limatype.StatusBroken, limatype.StatusInstalling: + return unregisterVM(ctx, distroName) } + logrus.Info("WSL VM is not running or does not exist, skipping deletion") return nil } func (l *LimaWslDriver) Start(ctx context.Context) (chan error, error) { logrus.Infof("Starting WSL VM") - status, err := store.GetWslStatus(l.Instance.Name) + status, err := getWslStatus(l.Instance.Name) if err != nil { return nil, err } distroName := "lima-" + l.Instance.Name - if status == store.StatusUninitialized { + if status == limatype.StatusUninitialized { if err := EnsureFs(ctx, l.Instance); err != nil { return nil, err } @@ -177,21 +251,6 @@ func (l *LimaWslDriver) Stop(ctx context.Context) error { return stopVM(ctx, distroName) } -func (l *LimaWslDriver) Unregister(ctx context.Context) error { - distroName := "lima-" + l.Instance.Name - status, err := store.GetWslStatus(l.Instance.Name) - if err != nil { - return err - } - switch status { - case store.StatusRunning, store.StatusStopped, store.StatusBroken, store.StatusInstalling: - return unregisterVM(ctx, distroName) - } - - logrus.Info("VM not registered, skipping unregistration") - return nil -} - // GuestAgentConn returns the guest agent connection, or nil (if forwarded by ssh). // As of 08-01-2024, github.com/mdlayher/vsock does not natively support vsock on // Windows, so use the winio library to create the connection. @@ -228,7 +287,7 @@ func (l *LimaWslDriver) Info() driver.Info { return info } -func (l *LimaWslDriver) Initialize(_ context.Context) error { +func (l *LimaWslDriver) Create(_ context.Context) error { return nil } @@ -236,10 +295,6 @@ func (l *LimaWslDriver) CreateDisk(_ context.Context) error { return nil } -func (l *LimaWslDriver) Register(_ context.Context) error { - return nil -} - func (l *LimaWslDriver) ChangeDisplayPassword(_ context.Context, _ string) error { return nil } diff --git a/pkg/driverutil/instance.go b/pkg/driverutil/instance.go index 2d865a806db..53ae318c52c 100644 --- a/pkg/driverutil/instance.go +++ b/pkg/driverutil/instance.go @@ -10,12 +10,12 @@ import ( "github.com/lima-vm/lima/v2/pkg/driver" "github.com/lima-vm/lima/v2/pkg/driver/external/server" + "github.com/lima-vm/lima/v2/pkg/limatype" "github.com/lima-vm/lima/v2/pkg/registry" - "github.com/lima-vm/lima/v2/pkg/store" ) // CreateConfiguredDriver creates a driver.ConfiguredDriver for the given instance. -func CreateConfiguredDriver(inst *store.Instance, sshLocalPort int) (*driver.ConfiguredDriver, error) { +func CreateConfiguredDriver(inst *limatype.Instance, sshLocalPort int) (*driver.ConfiguredDriver, error) { limaDriver := inst.Config.VMType extDriver, intDriver, exists := registry.Get(*limaDriver) if !exists { diff --git a/pkg/driverutil/vm.go b/pkg/driverutil/vm.go new file mode 100644 index 00000000000..8218650bb47 --- /dev/null +++ b/pkg/driverutil/vm.go @@ -0,0 +1,46 @@ +package driverutil + +import ( + "fmt" + + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/registry" + "github.com/sirupsen/logrus" +) + +func ResolveVMType(y *limatype.LimaYAML, filePath string) error { + if y.VMType != nil && *y.VMType != "" { + if err := validateConfigAgainstDriver(y, filePath, *y.VMType); err != nil { + return err + } + logrus.Debugf("Using specified vmType %q for %q", *y.VMType, filePath) + return nil + } + + // If VMType is not specified, we go with the default platform driver. + vmType := limatype.DefaultDriver() + if err := validateConfigAgainstDriver(y, filePath, vmType); err == nil { + return nil + } else { + return err + } +} + +func validateConfigAgainstDriver(y *limatype.LimaYAML, filePath, vmType string) error { + _, intDriver, exists := registry.Get(vmType) + if !exists { + return fmt.Errorf("vmType %q is not a registered driver", vmType) + } + // For now we only support internal drivers. + if intDriver == nil { + return fmt.Errorf("vmType %q is not an internal driver", vmType) + } + if err := intDriver.AcceptConfig(y, filePath); err != nil { + return err + } + if err := intDriver.FillConfig(y, filePath); err != nil { + return err + } + + return nil +} diff --git a/pkg/editutil/editutil.go b/pkg/editutil/editutil.go index 9c396a51315..2187e3ea60e 100644 --- a/pkg/editutil/editutil.go +++ b/pkg/editutil/editutil.go @@ -14,8 +14,8 @@ import ( "github.com/sirupsen/logrus" "github.com/lima-vm/lima/v2/pkg/editutil/editorcmd" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" - "github.com/lima-vm/lima/v2/pkg/store/filenames" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" ) func fileWarning(filename string) string { diff --git a/pkg/fileutils/download.go b/pkg/fileutils/download.go index 7bf3fea5372..e6d47f2ba9d 100644 --- a/pkg/fileutils/download.go +++ b/pkg/fileutils/download.go @@ -12,14 +12,14 @@ import ( "github.com/sirupsen/logrus" "github.com/lima-vm/lima/v2/pkg/downloader" - "github.com/lima-vm/lima/v2/pkg/limayaml" + "github.com/lima-vm/lima/v2/pkg/limatype" ) // ErrSkipped is returned when the downloader did not attempt to download the specified file. var ErrSkipped = errors.New("skipped to download") // DownloadFile downloads a file to the cache, optionally copying it to the destination. Returns path in cache. -func DownloadFile(ctx context.Context, dest string, f limayaml.File, decompress bool, description string, expectedArch limayaml.Arch) (string, error) { +func DownloadFile(ctx context.Context, dest string, f limatype.File, decompress bool, description string, expectedArch limatype.Arch) (string, error) { if f.Arch != expectedArch { return "", fmt.Errorf("%w: %q: unsupported arch: %q", ErrSkipped, f.Location, f.Arch) } @@ -47,7 +47,7 @@ func DownloadFile(ctx context.Context, dest string, f limayaml.File, decompress } // CachedFile checks if a file is in the cache, validating the digest if it is available. Returns path in cache. -func CachedFile(f limayaml.File) (string, error) { +func CachedFile(f limatype.File) (string, error) { res, err := downloader.Cached(f.Location, downloader.WithCache(), downloader.WithExpectedDigest(f.Digest)) diff --git a/pkg/hostagent/hostagent.go b/pkg/hostagent/hostagent.go index 98d0922a0bd..aa4940131cb 100644 --- a/pkg/hostagent/hostagent.go +++ b/pkg/hostagent/hostagent.go @@ -35,17 +35,18 @@ import ( "github.com/lima-vm/lima/v2/pkg/hostagent/dns" "github.com/lima-vm/lima/v2/pkg/hostagent/events" "github.com/lima-vm/lima/v2/pkg/instance/hostname" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/limayaml" "github.com/lima-vm/lima/v2/pkg/networks" "github.com/lima-vm/lima/v2/pkg/osutil" "github.com/lima-vm/lima/v2/pkg/portfwd" "github.com/lima-vm/lima/v2/pkg/sshutil" "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" ) type HostAgent struct { - instConfig *limayaml.LimaYAML + instConfig *limatype.LimaYAML sshLocalPort int udpDNSLocalPort int tcpDNSLocalPort int @@ -115,7 +116,7 @@ func New(instName string, stdout io.Writer, signalCh chan os.Signal, opts ...Opt if err != nil { return nil, err } - if *inst.Config.VMType == limayaml.WSL2 { + if *inst.Config.VMType == limatype.WSL2 { sshLocalPort = inst.SSHLocalPort } @@ -169,13 +170,13 @@ func New(instName string, stdout io.Writer, signalCh chan os.Signal, opts ...Opt for _, rule := range inst.Config.PortForwards { if rule.Ignore && rule.GuestPortRange[0] == 1 && rule.GuestPortRange[1] == 65535 { switch rule.Proto { - case limayaml.ProtoTCP: + case limatype.ProtoTCP: ignoreTCP = true logrus.Info("TCP port forwarding is disabled (except for SSH)") - case limayaml.ProtoUDP: + case limatype.ProtoUDP: ignoreUDP = true logrus.Info("UDP port forwarding is disabled") - case limayaml.ProtoAny: + case limatype.ProtoAny: ignoreTCP = true ignoreUDP = true logrus.Info("TCP (except for SSH) and UDP port forwarding is disabled") @@ -184,16 +185,16 @@ func New(instName string, stdout io.Writer, signalCh chan os.Signal, opts ...Opt break } } - rules := make([]limayaml.PortForward, 0, 3+len(inst.Config.PortForwards)) + rules := make([]limatype.PortForward, 0, 3+len(inst.Config.PortForwards)) // Block ports 22 and sshLocalPort on all IPs for _, port := range []int{sshGuestPort, sshLocalPort} { - rule := limayaml.PortForward{GuestIP: net.IPv4zero, GuestPort: port, Ignore: true} + rule := limatype.PortForward{GuestIP: net.IPv4zero, GuestPort: port, Ignore: true} limayaml.FillPortForwardDefaults(&rule, inst.Dir, inst.Config.User, inst.Param) rules = append(rules, rule) } rules = append(rules, inst.Config.PortForwards...) // Default forwards for all non-privileged ports from "127.0.0.1" and "::1" - rule := limayaml.PortForward{} + rule := limatype.PortForward{} limayaml.FillPortForwardDefaults(&rule, inst.Dir, inst.Config.User, inst.Param) rules = append(rules, rule) @@ -309,7 +310,7 @@ func (a *HostAgent) Run(ctx context.Context) error { } // WSL instance SSH address isn't known until after VM start - if *a.instConfig.VMType == limayaml.WSL2 { + if *a.instConfig.VMType == limatype.WSL2 { sshAddr, err := store.GetSSHAddress(a.instName) if err != nil { return err @@ -446,7 +447,7 @@ sudo chown -R "${USER}" /run/host-services` errs = append(errs, fmt.Errorf("stdout=%q, stderr=%q: %w", stdout, stderr, err)) } } - if *a.instConfig.MountType == limayaml.REVSSHFS && !*a.instConfig.Plain { + if *a.instConfig.MountType == limatype.REVSSHFS && !*a.instConfig.Plain { mounts, err := a.setupMounts() if err != nil { errs = append(errs, err) @@ -533,7 +534,7 @@ func (a *HostAgent) watchGuestAgentEvents(ctx context.Context) { // TODO: use vSock (when QEMU for macOS gets support for vSock) // Setup all socket forwards and defer their teardown - if *a.instConfig.VMType != limayaml.WSL2 { + if *a.instConfig.VMType != limatype.WSL2 { logrus.Debugf("Forwarding unix sockets") for _, rule := range a.instConfig.PortForwards { if rule.GuestSocket != "" { diff --git a/pkg/hostagent/mount.go b/pkg/hostagent/mount.go index 575ff236966..930274a2f79 100644 --- a/pkg/hostagent/mount.go +++ b/pkg/hostagent/mount.go @@ -13,7 +13,7 @@ import ( "github.com/sirupsen/logrus" "github.com/lima-vm/lima/v2/pkg/ioutilx" - "github.com/lima-vm/lima/v2/pkg/limayaml" + "github.com/lima-vm/lima/v2/pkg/limatype" ) type mount struct { @@ -36,7 +36,7 @@ func (a *HostAgent) setupMounts() ([]*mount, error) { return res, errors.Join(errs...) } -func (a *HostAgent) setupMount(m limayaml.Mount) (*mount, error) { +func (a *HostAgent) setupMount(m limatype.Mount) (*mount, error) { if err := os.MkdirAll(m.Location, 0o755); err != nil { return nil, err } diff --git a/pkg/hostagent/port.go b/pkg/hostagent/port.go index 5a945c24ac5..cf792386f87 100644 --- a/pkg/hostagent/port.go +++ b/pkg/hostagent/port.go @@ -11,22 +11,23 @@ import ( "github.com/sirupsen/logrus" "github.com/lima-vm/lima/v2/pkg/guestagent/api" + "github.com/lima-vm/lima/v2/pkg/limatype" "github.com/lima-vm/lima/v2/pkg/limayaml" ) type portForwarder struct { sshConfig *ssh.SSHConfig sshHostPort int - rules []limayaml.PortForward + rules []limatype.PortForward ignore bool - vmType limayaml.VMType + vmType limatype.VMType } const sshGuestPort = 22 var IPv4loopback1 = limayaml.IPv4loopback1 -func newPortForwarder(sshConfig *ssh.SSHConfig, sshHostPort int, rules []limayaml.PortForward, ignore bool, vmType limayaml.VMType) *portForwarder { +func newPortForwarder(sshConfig *ssh.SSHConfig, sshHostPort int, rules []limatype.PortForward, ignore bool, vmType limatype.VMType) *portForwarder { return &portForwarder{ sshConfig: sshConfig, sshHostPort: sshHostPort, @@ -36,7 +37,7 @@ func newPortForwarder(sshConfig *ssh.SSHConfig, sshHostPort int, rules []limayam } } -func hostAddress(rule limayaml.PortForward, guest *api.IPPort) string { +func hostAddress(rule limatype.PortForward, guest *api.IPPort) string { if rule.HostSocket != "" { return rule.HostSocket } @@ -57,7 +58,7 @@ func (pf *portForwarder) forwardingAddresses(guest *api.IPPort) (hostAddr, guest continue } switch rule.Proto { - case limayaml.ProtoTCP, limayaml.ProtoAny: + case limatype.ProtoTCP, limatype.ProtoAny: default: continue } diff --git a/pkg/hostagent/requirements.go b/pkg/hostagent/requirements.go index 1c09b42bf83..148d6785b67 100644 --- a/pkg/hostagent/requirements.go +++ b/pkg/hostagent/requirements.go @@ -12,7 +12,7 @@ import ( "github.com/lima-vm/sshocker/pkg/ssh" "github.com/sirupsen/logrus" - "github.com/lima-vm/lima/v2/pkg/limayaml" + "github.com/lima-vm/lima/v2/pkg/limatype" ) func (a *HostAgent) waitForRequirements(label string, requirements []requirement) error { @@ -149,7 +149,7 @@ it must not be created until the session reset is done. `, }) - if *a.instConfig.MountType == limayaml.REVSSHFS && len(a.instConfig.Mounts) > 0 { + if *a.instConfig.MountType == limatype.REVSSHFS && len(a.instConfig.Mounts) > 0 { req = append(req, requirement{ description: "sshfs binary to be installed", script: `#!/bin/bash @@ -216,7 +216,7 @@ Also see "/var/log/cloud-init-output.log" in the guest. }) } for _, probe := range a.instConfig.Probes { - if probe.Mode == limayaml.ProbeModeReadiness { + if probe.Mode == limatype.ProbeModeReadiness { req = append(req, requirement{ description: probe.Description, script: probe.Script, diff --git a/pkg/instance/ansible.go b/pkg/instance/ansible.go index c0f9b0f1773..2468804a208 100644 --- a/pkg/instance/ansible.go +++ b/pkg/instance/ansible.go @@ -13,14 +13,13 @@ import ( "github.com/goccy/go-yaml" "github.com/sirupsen/logrus" - "github.com/lima-vm/lima/v2/pkg/limayaml" - "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" ) -func runAnsibleProvision(ctx context.Context, inst *store.Instance) error { +func runAnsibleProvision(ctx context.Context, inst *limatype.Instance) error { for _, f := range inst.Config.Provision { - if f.Mode == limayaml.ProvisionModeAnsible { + if f.Mode == limatype.ProvisionModeAnsible { logrus.Infof("Waiting for ansible playbook %q", f.Playbook) if err := runAnsiblePlaybook(ctx, inst, f.Playbook); err != nil { return err @@ -30,7 +29,7 @@ func runAnsibleProvision(ctx context.Context, inst *store.Instance) error { return nil } -func runAnsiblePlaybook(ctx context.Context, inst *store.Instance, playbook string) error { +func runAnsiblePlaybook(ctx context.Context, inst *limatype.Instance, playbook string) error { inventory, err := createAnsibleInventory(inst) if err != nil { return err @@ -44,7 +43,7 @@ func runAnsiblePlaybook(ctx context.Context, inst *store.Instance, playbook stri return cmd.Run() } -func createAnsibleInventory(inst *store.Instance) (string, error) { +func createAnsibleInventory(inst *limatype.Instance) (string, error) { vars := map[string]any{ "ansible_connection": "ssh", "ansible_host": inst.Hostname, @@ -67,7 +66,7 @@ func createAnsibleInventory(inst *store.Instance) (string, error) { return inventory, os.WriteFile(inventory, bytes, 0o644) } -func getAnsibleEnvironment(inst *store.Instance) []string { +func getAnsibleEnvironment(inst *limatype.Instance) []string { env := os.Environ() for key, val := range inst.Config.Param { env = append(env, fmt.Sprintf("PARAM_%s=%s", key, val)) diff --git a/pkg/instance/clone.go b/pkg/instance/clone.go index 0e02197c28e..46336058d06 100644 --- a/pkg/instance/clone.go +++ b/pkg/instance/clone.go @@ -15,23 +15,25 @@ import ( continuityfs "github.com/containerd/continuity/fs" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/osutil" "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" ) -func Clone(_ context.Context, oldInst *store.Instance, newInstName string) (*store.Instance, error) { +func Clone(_ context.Context, oldInst *limatype.Instance, newInstName string) (*limatype.Instance, error) { if newInstName == "" { return nil, errors.New("got empty instName") } if oldInst.Name == newInstName { return nil, fmt.Errorf("new instance name %q must be different from %q", newInstName, oldInst.Name) } - if oldInst.Status == store.StatusRunning { + if oldInst.Status == limatype.StatusRunning { return nil, errors.New("cannot clone a running instance") } - newInstDir, err := store.InstanceDir(newInstName) + newInstDir, err := dirnames.InstanceDir(newInstName) if err != nil { return nil, err } diff --git a/pkg/instance/create.go b/pkg/instance/create.go index 377f86382f4..db944c3f699 100644 --- a/pkg/instance/create.go +++ b/pkg/instance/create.go @@ -12,14 +12,16 @@ import ( "github.com/lima-vm/lima/v2/pkg/cidata" "github.com/lima-vm/lima/v2/pkg/driverutil" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/limayaml" "github.com/lima-vm/lima/v2/pkg/osutil" "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" "github.com/lima-vm/lima/v2/pkg/version" ) -func Create(ctx context.Context, instName string, instConfig []byte, saveBrokenYAML bool) (*store.Instance, error) { +func Create(ctx context.Context, instName string, instConfig []byte, saveBrokenYAML bool) (*limatype.Instance, error) { if instName == "" { return nil, errors.New("got empty instName") } @@ -27,7 +29,7 @@ func Create(ctx context.Context, instName string, instConfig []byte, saveBrokenY return nil, errors.New("got empty instConfig") } - instDir, err := store.InstanceDir(instName) + instDir, err := dirnames.InstanceDir(instName) if err != nil { return nil, err } @@ -47,6 +49,9 @@ func Create(ctx context.Context, instName string, instConfig []byte, saveBrokenY if err != nil { return nil, err } + if err := driverutil.ResolveVMType(loadedInstConfig, filePath); err != nil { + return nil, fmt.Errorf("failed to accept config for %q: %w", filePath, err) + } if err := limayaml.Validate(loadedInstConfig, true); err != nil { if !saveBrokenYAML { return nil, err @@ -80,7 +85,7 @@ func Create(ctx context.Context, instName string, instConfig []byte, saveBrokenY return nil, fmt.Errorf("failed to create driver instance: %w", err) } - if err := limaDriver.Register(ctx); err != nil { + if err := limaDriver.Create(ctx); err != nil { return nil, err } diff --git a/pkg/instance/delete.go b/pkg/instance/delete.go index 7c53fdf1c46..df0954db5e6 100644 --- a/pkg/instance/delete.go +++ b/pkg/instance/delete.go @@ -10,15 +10,15 @@ import ( "os" "github.com/lima-vm/lima/v2/pkg/driverutil" - "github.com/lima-vm/lima/v2/pkg/store" + "github.com/lima-vm/lima/v2/pkg/limatype" ) -func Delete(ctx context.Context, inst *store.Instance, force bool) error { +func Delete(ctx context.Context, inst *limatype.Instance, force bool) error { if inst.Protected { return errors.New("instance is protected to prohibit accidental removal (Hint: use `limactl unprotect`)") } - if !force && inst.Status != store.StatusStopped { - return fmt.Errorf("expected status %q, got %q", store.StatusStopped, inst.Status) + if !force && inst.Status != limatype.StatusStopped { + return fmt.Errorf("expected status %q, got %q", limatype.StatusStopped, inst.Status) } StopForcibly(inst) @@ -35,11 +35,11 @@ func Delete(ctx context.Context, inst *store.Instance, force bool) error { return nil } -func unregister(ctx context.Context, inst *store.Instance) error { +func unregister(ctx context.Context, inst *limatype.Instance) error { limaDriver, err := driverutil.CreateConfiguredDriver(inst, 0) if err != nil { return fmt.Errorf("failed to create driver instance: %w", err) } - return limaDriver.Unregister(ctx) + return limaDriver.Delete(ctx) } diff --git a/pkg/instance/restart.go b/pkg/instance/restart.go index 91716a17f59..0635edf249e 100644 --- a/pkg/instance/restart.go +++ b/pkg/instance/restart.go @@ -8,13 +8,13 @@ import ( "github.com/sirupsen/logrus" + "github.com/lima-vm/lima/v2/pkg/limatype" networks "github.com/lima-vm/lima/v2/pkg/networks/reconcile" - "github.com/lima-vm/lima/v2/pkg/store" ) const launchHostAgentForeground = false -func Restart(ctx context.Context, inst *store.Instance) error { +func Restart(ctx context.Context, inst *limatype.Instance) error { if err := StopGracefully(ctx, inst, true); err != nil { return err } @@ -30,7 +30,7 @@ func Restart(ctx context.Context, inst *store.Instance) error { return nil } -func RestartForcibly(ctx context.Context, inst *store.Instance) error { +func RestartForcibly(ctx context.Context, inst *limatype.Instance) error { logrus.Info("Restarting the instance forcibly") StopForcibly(inst) diff --git a/pkg/instance/start.go b/pkg/instance/start.go index ec118772013..d973eab8311 100644 --- a/pkg/instance/start.go +++ b/pkg/instance/start.go @@ -26,10 +26,10 @@ import ( "github.com/lima-vm/lima/v2/pkg/fileutils" hostagentevents "github.com/lima-vm/lima/v2/pkg/hostagent/events" "github.com/lima-vm/lima/v2/pkg/imgutil/proxyimgutil" - "github.com/lima-vm/lima/v2/pkg/limayaml" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/registry" "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" "github.com/lima-vm/lima/v2/pkg/usrlocalsharelima" ) @@ -40,7 +40,7 @@ const DefaultWatchHostAgentEventsTimeout = 10 * time.Minute // ensureNerdctlArchiveCache prefetches the nerdctl-full-VERSION-GOOS-GOARCH.tar.gz archive // into the cache before launching the hostagent process, so that we can show the progress in tty. // https://github.com/lima-vm/lima/issues/326 -func ensureNerdctlArchiveCache(ctx context.Context, y *limayaml.LimaYAML, created bool) (string, error) { +func ensureNerdctlArchiveCache(ctx context.Context, y *limatype.LimaYAML, created bool) (string, error) { if !*y.Containerd.System && !*y.Containerd.User { // nerdctl archive is not needed return "", nil @@ -79,7 +79,7 @@ type Prepared struct { } // Prepare ensures the disk, the nerdctl archive, etc. -func Prepare(ctx context.Context, inst *store.Instance) (*Prepared, error) { +func Prepare(ctx context.Context, inst *limatype.Instance) (*Prepared, error) { var guestAgent string if !*inst.Config.Plain { var err error @@ -97,7 +97,7 @@ func Prepare(ctx context.Context, inst *store.Instance) (*Prepared, error) { return nil, err } - if err := limaDriver.Initialize(ctx); err != nil { + if err := limaDriver.Create(ctx); err != nil { return nil, err } @@ -179,7 +179,7 @@ func Prepare(ctx context.Context, inst *store.Instance) (*Prepared, error) { // shut down again. // // Start calls Prepare by itself, so you do not need to call Prepare manually before calling Start. -func Start(ctx context.Context, inst *store.Instance, limactl string, launchHostAgentForeground bool) error { +func Start(ctx context.Context, inst *limatype.Instance, limactl string, launchHostAgentForeground bool) error { haPIDPath := filepath.Join(inst.Dir, filenames.HostAgentPID) if _, err := os.Stat(haPIDPath); !errors.Is(err, os.ErrNotExist) { return fmt.Errorf("instance %q seems running (hint: remove %q if the instance is not actually running)", inst.Name, haPIDPath) @@ -297,7 +297,7 @@ func waitHostAgentStart(_ context.Context, haPIDPath, haStderrPath string) error } } -func watchHostAgentEvents(ctx context.Context, inst *store.Instance, haStdoutPath, haStderrPath string, begin time.Time) error { +func watchHostAgentEvents(ctx context.Context, inst *limatype.Instance, haStdoutPath, haStderrPath string, begin time.Time) error { ctx, cancel := context.WithTimeout(ctx, watchHostAgentTimeout(ctx)) defer cancel() @@ -382,7 +382,7 @@ func LimactlShellCmd(instName string) string { return shellCmd } -func ShowMessage(inst *store.Instance) error { +func ShowMessage(inst *limatype.Instance) error { if inst.Message == "" { return nil } @@ -409,7 +409,7 @@ func ShowMessage(inst *store.Instance) error { // prepareDiffDisk checks the disk size difference between inst.Disk and yaml.Disk. // If there is no diffDisk, return nil (the instance has not been initialized or started yet). -func prepareDiffDisk(inst *store.Instance) error { +func prepareDiffDisk(inst *limatype.Instance) error { diffDisk := filepath.Join(inst.Dir, filenames.DiffDisk) // Handle the instance initialization diff --git a/pkg/instance/stop.go b/pkg/instance/stop.go index 2cc36c75beb..2eb4211c7e4 100644 --- a/pkg/instance/stop.go +++ b/pkg/instance/stop.go @@ -15,18 +15,19 @@ import ( "github.com/sirupsen/logrus" hostagentevents "github.com/lima-vm/lima/v2/pkg/hostagent/events" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/osutil" "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/filenames" ) -func StopGracefully(ctx context.Context, inst *store.Instance, isRestart bool) error { - if inst.Status != store.StatusRunning { +func StopGracefully(ctx context.Context, inst *limatype.Instance, isRestart bool) error { + if inst.Status != limatype.StatusRunning { if isRestart { logrus.Warn("The instance is not running, continuing with the restart") return nil } - return fmt.Errorf("expected status %q, got %q (maybe use `limactl stop -f`?)", store.StatusRunning, inst.Status) + return fmt.Errorf("expected status %q, got %q (maybe use `limactl stop -f`?)", limatype.StatusRunning, inst.Status) } begin := time.Now() // used for logrus propagation @@ -45,7 +46,7 @@ func StopGracefully(ctx context.Context, inst *store.Instance, isRestart bool) e return waitForInstanceShutdown(ctx, inst) } -func waitForHostAgentTermination(ctx context.Context, inst *store.Instance, begin time.Time) error { +func waitForHostAgentTermination(ctx context.Context, inst *limatype.Instance, begin time.Time) error { ctx, cancel := context.WithTimeout(ctx, 3*time.Minute+10*time.Second) defer cancel() @@ -75,7 +76,7 @@ func waitForHostAgentTermination(ctx context.Context, inst *store.Instance, begi return nil } -func waitForInstanceShutdown(ctx context.Context, inst *store.Instance) error { +func waitForInstanceShutdown(ctx context.Context, inst *limatype.Instance) error { ctx, cancel := context.WithTimeout(ctx, 3*time.Minute) defer cancel() @@ -90,7 +91,7 @@ func waitForInstanceShutdown(ctx context.Context, inst *store.Instance) error { return fmt.Errorf("failed to inspect instance status: %w", err) } - if updatedInst.Status == store.StatusStopped { + if updatedInst.Status == limatype.StatusStopped { logrus.Infof("The instance %s has shut down", updatedInst.Name) return nil } @@ -100,7 +101,7 @@ func waitForInstanceShutdown(ctx context.Context, inst *store.Instance) error { } } -func StopForcibly(inst *store.Instance) { +func StopForcibly(inst *limatype.Instance) { if inst.DriverPID > 0 { logrus.Infof("Sending SIGKILL to the %s driver process %d", inst.VMType, inst.DriverPID) if err := osutil.SysKill(inst.DriverPID, osutil.SigKill); err != nil { diff --git a/pkg/limainfo/limainfo.go b/pkg/limainfo/limainfo.go index 9f8fdfc9bc2..f1280b985e9 100644 --- a/pkg/limainfo/limainfo.go +++ b/pkg/limainfo/limainfo.go @@ -5,13 +5,16 @@ package limainfo import ( "errors" + "fmt" "io/fs" "github.com/sirupsen/logrus" + "github.com/lima-vm/lima/v2/pkg/driverutil" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" "github.com/lima-vm/lima/v2/pkg/limayaml" "github.com/lima-vm/lima/v2/pkg/registry" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" "github.com/lima-vm/lima/v2/pkg/templatestore" "github.com/lima-vm/lima/v2/pkg/usrlocalsharelima" "github.com/lima-vm/lima/v2/pkg/version" @@ -20,11 +23,11 @@ import ( type LimaInfo struct { Version string `json:"version"` Templates []templatestore.Template `json:"templates"` - DefaultTemplate *limayaml.LimaYAML `json:"defaultTemplate"` + DefaultTemplate *limatype.LimaYAML `json:"defaultTemplate"` LimaHome string `json:"limaHome"` VMTypes []string `json:"vmTypes"` // since Lima v0.14.2 VMTypesEx map[string]DriverExt `json:"vmTypesEx"` // since Lima v2.0.0 - GuestAgents map[limayaml.Arch]GuestAgent `json:"guestAgents"` // since Lima v1.1.0 + GuestAgents map[limatype.Arch]GuestAgent `json:"guestAgents"` // since Lima v1.1.0 } type DriverExt struct { @@ -47,6 +50,9 @@ func New() (*LimaInfo, error) { if err != nil { return nil, err } + if err := driverutil.ResolveVMType(y, ""); err != nil { + return nil, fmt.Errorf("failed to accept config for %q: %w", "", err) + } reg := registry.List() if len(reg) == 0 { @@ -66,7 +72,7 @@ func New() (*LimaInfo, error) { DefaultTemplate: y, VMTypes: vmTypes, VMTypesEx: vmTypesEx, - GuestAgents: make(map[limayaml.Arch]GuestAgent), + GuestAgents: make(map[limatype.Arch]GuestAgent), } info.Templates, err = templatestore.Templates() if err != nil { @@ -76,8 +82,8 @@ func New() (*LimaInfo, error) { if err != nil { return nil, err } - for _, arch := range limayaml.ArchTypes { - bin, err := usrlocalsharelima.GuestAgentBinary(limayaml.LINUX, arch) + for _, arch := range limatype.ArchTypes { + bin, err := usrlocalsharelima.GuestAgentBinary(limatype.LINUX, arch) if err != nil { if errors.Is(err, fs.ErrNotExist) { logrus.WithError(err).Debugf("Failed to resolve the guest agent binary for %q", arch) diff --git a/pkg/limatmpl/embed.go b/pkg/limatmpl/embed.go index 6c2a7b91e1e..9ccbe56489a 100644 --- a/pkg/limatmpl/embed.go +++ b/pkg/limatmpl/embed.go @@ -18,9 +18,9 @@ import ( "github.com/coreos/go-semver/semver" "github.com/sirupsen/logrus" - "github.com/lima-vm/lima/v2/pkg/limayaml" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" - "github.com/lima-vm/lima/v2/pkg/store/filenames" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/version/versionutil" "github.com/lima-vm/lima/v2/pkg/yqutil" ) @@ -106,7 +106,7 @@ func (tmpl *Template) embedAllBases(ctx context.Context, embedAll, defaultBase b return nil } -func (tmpl *Template) embedBase(ctx context.Context, baseLocator limayaml.LocatorWithDigest, embedAll bool, seen map[string]bool) error { +func (tmpl *Template) embedBase(ctx context.Context, baseLocator limatype.LocatorWithDigest, embedAll bool, seen map[string]bool) error { logrus.Debugf("Embedding base %q in template %q", baseLocator.URL, tmpl.Locator) if err := tmpl.Unmarshal(); err != nil { return err @@ -643,7 +643,7 @@ func (tmpl *Template) embedAllScripts(ctx context.Context, embedAll bool) error } newName := "script" switch p.Mode { - case limayaml.ProvisionModeData: + case limatype.ProvisionModeData: newName = "content" if p.Content != nil { continue diff --git a/pkg/limatmpl/embed_test.go b/pkg/limatmpl/embed_test.go index 16f7ad638c2..97781ec413b 100644 --- a/pkg/limatmpl/embed_test.go +++ b/pkg/limatmpl/embed_test.go @@ -15,6 +15,7 @@ import ( "gotest.tools/v3/assert" "gotest.tools/v3/assert/cmp" + "github.com/lima-vm/lima/v2/pkg/limatype" "github.com/lima-vm/lima/v2/pkg/limayaml" ) @@ -479,7 +480,7 @@ func RunEmbedTest(t *testing.T, tc embedTestCase) { err = tmpl.Unmarshal() assert.NilError(t, err, tc.description) - var expected limayaml.LimaYAML + var expected limatype.LimaYAML err = limayaml.Unmarshal([]byte(tc.expected), &expected, "expected") assert.NilError(t, err, tc.description) diff --git a/pkg/limatmpl/locator.go b/pkg/limatmpl/locator.go index 7940e16405a..98e664af711 100644 --- a/pkg/limatmpl/locator.go +++ b/pkg/limatmpl/locator.go @@ -20,6 +20,7 @@ import ( "github.com/lima-vm/lima/v2/pkg/identifiers" "github.com/lima-vm/lima/v2/pkg/ioutilx" + "github.com/lima-vm/lima/v2/pkg/limatype" "github.com/lima-vm/lima/v2/pkg/limayaml" "github.com/lima-vm/lima/v2/pkg/templatestore" ) @@ -131,17 +132,17 @@ func Read(ctx context.Context, name, locator string) (*Template, error) { var imageURLRegex = regexp.MustCompile(`\.(img|qcow2|raw|iso)(\.(gz|xz|bz2|zstd))?$`) // Image architecture will be guessed based on the presence of arch keywords. -var archKeywords = map[string]limayaml.Arch{ - "aarch64": limayaml.AARCH64, - "amd64": limayaml.X8664, - "arm64": limayaml.AARCH64, - "armhf": limayaml.ARMV7L, - "armv7l": limayaml.ARMV7L, - "ppc64el": limayaml.PPC64LE, - "ppc64le": limayaml.PPC64LE, - "riscv64": limayaml.RISCV64, - "s390x": limayaml.S390X, - "x86_64": limayaml.X8664, +var archKeywords = map[string]limatype.Arch{ + "aarch64": limatype.AARCH64, + "amd64": limatype.X8664, + "arm64": limatype.AARCH64, + "armhf": limatype.ARMV7L, + "armv7l": limatype.ARMV7L, + "ppc64el": limatype.PPC64LE, + "ppc64le": limatype.PPC64LE, + "riscv64": limatype.RISCV64, + "s390x": limatype.S390X, + "x86_64": limatype.X8664, } // These generic tags will be stripped from an image name before turning it into an instance name. @@ -174,7 +175,7 @@ func imageTemplate(tmpl *Template, locator string) bool { return false } - var imageArch limayaml.Arch + var imageArch limatype.Arch for keyword, arch := range archKeywords { pattern := fmt.Sprintf(`\b%s\b`, keyword) if regexp.MustCompile(pattern).MatchString(locator) { @@ -183,7 +184,7 @@ func imageTemplate(tmpl *Template, locator string) bool { } } if imageArch == "" { - imageArch = limayaml.NewArch(runtime.GOARCH) + imageArch = limatype.NewArch(runtime.GOARCH) logrus.Warnf("cannot determine image arch from URL %q; assuming %q", locator, imageArch) } template := `arch: %q diff --git a/pkg/limatmpl/locator_test.go b/pkg/limatmpl/locator_test.go index b3cf8bdada1..b611cf3bc75 100644 --- a/pkg/limatmpl/locator_test.go +++ b/pkg/limatmpl/locator_test.go @@ -11,7 +11,7 @@ import ( "gotest.tools/v3/assert" "github.com/lima-vm/lima/v2/pkg/limatmpl" - "github.com/lima-vm/lima/v2/pkg/limayaml" + "github.com/lima-vm/lima/v2/pkg/limatype" ) func TestInstNameFromImageURL(t *testing.T) { @@ -48,7 +48,7 @@ func TestInstNameFromImageURL(t *testing.T) { assert.Equal(t, name, "my-arch-aarch64") }) t.Run("removes native arch", func(t *testing.T) { - arch := limayaml.NewArch(runtime.GOARCH) + arch := limatype.NewArch(runtime.GOARCH) image := fmt.Sprintf("linux_cloudimg.base-%s.qcow2.gz", arch) name := limatmpl.InstNameFromImageURL(image, arch) assert.Equal(t, name, "linux") diff --git a/pkg/limatmpl/template.go b/pkg/limatmpl/template.go index 1ca56a3c338..5ac277f9306 100644 --- a/pkg/limatmpl/template.go +++ b/pkg/limatmpl/template.go @@ -6,6 +6,7 @@ package limatmpl import ( "strings" + "github.com/lima-vm/lima/v2/pkg/limatype" "github.com/lima-vm/lima/v2/pkg/limayaml" ) @@ -15,7 +16,7 @@ type Template struct { // The following fields are only used when the template represents a YAML config file. Name string // instance name, may be inferred from locator - Config *limayaml.LimaYAML + Config *limatype.LimaYAML expr strings.Builder // yq expression to update template } @@ -33,7 +34,7 @@ func (tmpl *Template) ClearOnError(err error) error { // tmpl.Bytes is expected to set tmpl.Config back to nil. func (tmpl *Template) Unmarshal() error { if tmpl.Config == nil { - tmpl.Config = &limayaml.LimaYAML{} + tmpl.Config = &limatype.LimaYAML{} if err := limayaml.Unmarshal(tmpl.Bytes, tmpl.Config, tmpl.Locator); err != nil { tmpl.Config = nil return err diff --git a/pkg/store/dirnames/dirnames.go b/pkg/limatype/dirnames/dirnames.go similarity index 66% rename from pkg/store/dirnames/dirnames.go rename to pkg/limatype/dirnames/dirnames.go index 43607f9a012..670de2273f3 100644 --- a/pkg/store/dirnames/dirnames.go +++ b/pkg/limatype/dirnames/dirnames.go @@ -8,8 +8,10 @@ import ( "fmt" "os" "path/filepath" + "strings" - "github.com/lima-vm/lima/v2/pkg/store/filenames" + "github.com/lima-vm/lima/v2/pkg/identifiers" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" ) // DotLima is a directory that appears under the home directory. @@ -74,3 +76,30 @@ func LimaTemplatesDir() (string, error) { } return filepath.Join(limaDir, filenames.TemplatesDir), nil } + +// InstanceDir returns the instance dir. +// InstanceDir does not check whether the instance exists. +func InstanceDir(name string) (string, error) { + if err := ValidateInstName(name); err != nil { + return "", err + } + limaDir, err := LimaDir() + if err != nil { + return "", err + } + dir := filepath.Join(limaDir, name) + return dir, nil +} + +// ValidateInstName checks if the name is a valid instance name. For this it needs to +// be a valid identifier, and not end in .yml or .yaml (case insensitively). +func ValidateInstName(name string) error { + if err := identifiers.Validate(name); err != nil { + return fmt.Errorf("instance name %q is not a valid identifier: %w", name, err) + } + lower := strings.ToLower(name) + if strings.HasSuffix(lower, ".yml") || strings.HasSuffix(lower, ".yaml") { + return fmt.Errorf("instance name %q must not end with .yml or .yaml suffix", name) + } + return nil +} diff --git a/pkg/store/filenames/filenames.go b/pkg/limatype/filenames/filenames.go similarity index 100% rename from pkg/store/filenames/filenames.go rename to pkg/limatype/filenames/filenames.go diff --git a/pkg/limatype/limainstance.go b/pkg/limatype/limainstance.go new file mode 100644 index 00000000000..b77bfa86f86 --- /dev/null +++ b/pkg/limatype/limainstance.go @@ -0,0 +1,107 @@ +package limatype + +import ( + "encoding/json" + "errors" + "os" + "path/filepath" + + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" +) + +type Status = string + +const ( + StatusUnknown Status = "" + StatusUninitialized Status = "Uninitialized" + StatusInstalling Status = "Installing" + StatusBroken Status = "Broken" + StatusStopped Status = "Stopped" + StatusRunning Status = "Running" +) + +type Instance struct { + Name string `json:"name"` + // Hostname, not HostName (corresponds to SSH's naming convention) + Hostname string `json:"hostname"` + Status Status `json:"status"` + Dir string `json:"dir"` + VMType VMType `json:"vmType"` + Arch Arch `json:"arch"` + CPUType string `json:"cpuType"` + CPUs int `json:"cpus,omitempty"` + Memory int64 `json:"memory,omitempty"` // bytes + Disk int64 `json:"disk,omitempty"` // bytes + Message string `json:"message,omitempty"` + AdditionalDisks []Disk `json:"additionalDisks,omitempty"` + Networks []Network `json:"network,omitempty"` + SSHLocalPort int `json:"sshLocalPort,omitempty"` + SSHConfigFile string `json:"sshConfigFile,omitempty"` + HostAgentPID int `json:"hostAgentPID,omitempty"` + DriverPID int `json:"driverPID,omitempty"` + Errors []error `json:"errors,omitempty"` + Config *LimaYAML `json:"config,omitempty"` + SSHAddress string `json:"sshAddress,omitempty"` + Protected bool `json:"protected"` + LimaVersion string `json:"limaVersion"` + Param map[string]string `json:"param,omitempty"` +} + +// Protect protects the instance to prohibit accidental removal. +// Protect does not return an error even when the instance is already protected. +func (inst *Instance) Protect() error { + protected := filepath.Join(inst.Dir, filenames.Protected) + // TODO: Do an equivalent of `chmod +a "everyone deny delete,delete_child,file_inherit,directory_inherit"` + // https://github.com/lima-vm/lima/issues/1595 + if err := os.WriteFile(protected, nil, 0o400); err != nil { + return err + } + inst.Protected = true + return nil +} + +// Unprotect unprotects the instance. +// Unprotect does not return an error even when the instance is already unprotected. +func (inst *Instance) Unprotect() error { + protected := filepath.Join(inst.Dir, filenames.Protected) + if err := os.RemoveAll(protected); err != nil { + return err + } + inst.Protected = false + return nil +} + +func (inst *Instance) MarshalJSON() ([]byte, error) { + type Alias Instance + errorsAsStrings := make([]string, len(inst.Errors)) + for i, err := range inst.Errors { + if err != nil { + errorsAsStrings[i] = err.Error() + } + } + return json.Marshal(&struct { + *Alias + Errors []string `json:"errors,omitempty"` + }{ + Alias: (*Alias)(inst), + Errors: errorsAsStrings, + }) +} + +func (inst *Instance) UnmarshalJSON(data []byte) error { + type Alias Instance + aux := &struct { + *Alias + Errors []string `json:"errors,omitempty"` + }{ + Alias: (*Alias)(inst), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + inst.Errors = nil + for _, msg := range aux.Errors { + inst.Errors = append(inst.Errors, errors.New(msg)) + } + return nil +} diff --git a/pkg/limayaml/limayaml.go b/pkg/limatype/limayaml.go similarity index 92% rename from pkg/limayaml/limayaml.go rename to pkg/limatype/limayaml.go index a21c4aa5cf0..5963010be90 100644 --- a/pkg/limayaml/limayaml.go +++ b/pkg/limatype/limayaml.go @@ -1,12 +1,15 @@ // SPDX-FileCopyrightText: Copyright The Lima Authors // SPDX-License-Identifier: Apache-2.0 -package limayaml +package limatype import ( "net" + "runtime" "github.com/opencontainers/go-digest" + "github.com/sirupsen/logrus" + "golang.org/x/sys/cpu" ) type LimaYAML struct { @@ -319,3 +322,79 @@ type CACertificates struct { Files []string `yaml:"files,omitempty" json:"files,omitempty" jsonschema:"nullable"` Certs []string `yaml:"certs,omitempty" json:"certs,omitempty" jsonschema:"nullable"` } + +func NewOS(osname string) OS { + switch osname { + case "linux": + return LINUX + default: + logrus.Warnf("Unknown os: %s", osname) + return osname + } +} + +func Goarm() int { + if runtime.GOOS != "linux" { + return 0 + } + if runtime.GOARCH != "arm" { + return 0 + } + if cpu.ARM.HasVFPv3 { + return 7 + } + if cpu.ARM.HasVFP { + return 6 + } + return 5 // default +} + +func NewArch(arch string) Arch { + switch arch { + case "amd64": + return X8664 + case "arm64": + return AARCH64 + case "arm": + arm := Goarm() + if arm == 7 { + return ARMV7L + } + logrus.Warnf("Unknown arm: %d", arm) + return arch + case "ppc64le": + return PPC64LE + case "riscv64": + return RISCV64 + case "s390x": + return S390X + default: + logrus.Warnf("Unknown arch: %s", arch) + return arch + } +} + +func NewVMType(driver string) VMType { + switch driver { + case "vz": + return VZ + case "qemu": + return QEMU + case "wsl2": + return WSL2 + default: + logrus.Warnf("Unknown driver: %s", driver) + return driver + } +} + +func DefaultDriver() VMType { + switch runtime.GOOS { + case "darwin": + return VZ + case "windows": + return WSL2 + default: + return QEMU + } +} diff --git a/pkg/limayaml/defaults.go b/pkg/limayaml/defaults.go index 1ab059a7645..d5a48f51f21 100644 --- a/pkg/limayaml/defaults.go +++ b/pkg/limayaml/defaults.go @@ -21,24 +21,22 @@ import ( "sync" "text/template" - "github.com/coreos/go-semver/semver" "github.com/docker/go-units" "github.com/goccy/go-yaml" "github.com/pbnjay/memory" "github.com/sirupsen/logrus" - "golang.org/x/sys/cpu" "github.com/lima-vm/lima/v2/pkg/instance/hostname" "github.com/lima-vm/lima/v2/pkg/ioutilx" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/localpathutil" . "github.com/lima-vm/lima/v2/pkg/must" "github.com/lima-vm/lima/v2/pkg/networks" "github.com/lima-vm/lima/v2/pkg/osutil" "github.com/lima-vm/lima/v2/pkg/ptr" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" - "github.com/lima-vm/lima/v2/pkg/store/filenames" "github.com/lima-vm/lima/v2/pkg/version" - "github.com/lima-vm/lima/v2/pkg/version/versionutil" ) const ( @@ -64,10 +62,10 @@ var ( var defaultContainerdYAML []byte type ContainerdYAML struct { - Archives []File + Archives []limatype.File } -func defaultContainerdArchives() []File { +func defaultContainerdArchives() []limatype.File { var containerd ContainerdYAML err := yaml.UnmarshalWithOptions(defaultContainerdYAML, &containerd, yaml.Strict()) if err != nil { @@ -77,8 +75,8 @@ func defaultContainerdArchives() []File { } // FirstUsernetIndex gets the index of first usernet network under l.Network[]. Returns -1 if no usernet network found. -func FirstUsernetIndex(l *LimaYAML) int { - return slices.IndexFunc(l.Networks, func(network Network) bool { return networks.IsUsernet(network.Lima) }) +func FirstUsernetIndex(l *limatype.LimaYAML) int { + return slices.IndexFunc(l.Networks, func(network limatype.Network) bool { return networks.IsUsernet(network.Lima) }) } func MACAddress(uniqueID string) string { @@ -137,21 +135,10 @@ func defaultGuestInstallPrefix() string { // - Networks are appended in d, y, o order // - DNS are picked from the highest priority where DNS is not empty. // - CACertificates Files and Certs are uniquely appended in d, y, o order -func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) { +func FillDefault(y, d, o *limatype.LimaYAML, filePath string, warn bool) { instDir := filepath.Dir(filePath) - // existingLimaVersion can be empty if the instance was created with Lima prior to v0.20, - var existingLimaVersion string - if !isExistingInstanceDir(instDir) { - existingLimaVersion = version.Version - } else { - limaVersionFile := filepath.Join(instDir, filenames.LimaVersion) - if b, err := os.ReadFile(limaVersionFile); err == nil { - existingLimaVersion = strings.TrimSpace(string(b)) - } else if !errors.Is(err, os.ErrNotExist) { - logrus.WithError(err).Warnf("Failed to read %q", limaVersionFile) - } - } + existingLimaVersion := ExistingLimaVersion(instDir) if y.User.Name == nil { y.User.Name = d.User.Name @@ -221,7 +208,7 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) { if o.VMType != nil { y.VMType = o.VMType } - y.VMType = ptr.Of(ResolveVMType(y, d, o, filePath)) + if y.OS == nil { y.OS = d.OS } @@ -252,7 +239,7 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) { } if y.VMOpts.QEMU.CPUType == nil { - y.VMOpts.QEMU.CPUType = CPUType{} + y.VMOpts.QEMU.CPUType = limatype.CPUType{} } // TODO: This check should be removed when we completely eliminate `CPUType` from limayaml. if len(y.CPUType) > 0 { @@ -330,9 +317,6 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) { if o.Video.VNC.Display != nil { y.Video.VNC.Display = o.Video.VNC.Display } - if (y.Video.VNC.Display == nil || *y.Video.VNC.Display == "") && *y.VMType == QEMU { - y.Video.VNC.Display = ptr.Of("127.0.0.1:0,to=9") - } if y.Firmware.LegacyBIOS == nil { y.Firmware.LegacyBIOS = d.Firmware.LegacyBIOS @@ -423,12 +407,12 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) { for i := range y.Provision { provision := &y.Provision[i] if provision.Mode == "" { - provision.Mode = ProvisionModeSystem + provision.Mode = limatype.ProvisionModeSystem } - if provision.Mode == ProvisionModeDependency && provision.SkipDefaultDependencyResolution == nil { + if provision.Mode == limatype.ProvisionModeDependency && provision.SkipDefaultDependencyResolution == nil { provision.SkipDefaultDependencyResolution = ptr.Of(false) } - if provision.Mode == ProvisionModeData { + if provision.Mode == limatype.ProvisionModeData { if provision.Content == nil { provision.Content = ptr.Of("") } else { @@ -509,7 +493,7 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) { } if y.Containerd.User == nil { switch *y.Arch { - case X8664, AARCH64: + case limatype.X8664, limatype.AARCH64: y.Containerd.User = ptr.Of(true) default: y.Containerd.User = ptr.Of(false) @@ -531,7 +515,7 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) { for i := range y.Probes { probe := &y.Probes[i] if probe.Mode == "" { - probe.Mode = ProbeModeReadiness + probe.Mode = limatype.ProbeModeReadiness } if probe.Description == "" { probe.Description = fmt.Sprintf("user probe %d/%d", i+1, len(y.Probes)) @@ -584,7 +568,7 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) { y.PropagateProxyEnv = ptr.Of(true) } - networks := make([]Network, 0, len(d.Networks)+len(y.Networks)+len(o.Networks)) + networks := make([]limatype.Network, 0, len(d.Networks)+len(y.Networks)+len(o.Networks)) iface := make(map[string]int) for _, nw := range slices.Concat(d.Networks, y.Networks, o.Networks) { if i, ok := iface[nw.Interface]; ok { @@ -631,15 +615,6 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) { } y.MountTypesUnsupported = slices.Concat(o.MountTypesUnsupported, y.MountTypesUnsupported, d.MountTypesUnsupported) - mountTypesUnsupported := make(map[string]struct{}) - for _, f := range y.MountTypesUnsupported { - mountTypesUnsupported[f] = struct{}{} - } - - if runtime.GOOS == "windows" { - // QEMU for Windows does not support 9p - mountTypesUnsupported[NINEP] = struct{}{} - } // MountType has to be resolved before resolving Mounts if y.MountType == nil { @@ -648,28 +623,6 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) { if o.MountType != nil { y.MountType = o.MountType } - if y.MountType == nil || *y.MountType == "" || *y.MountType == "default" { - switch *y.VMType { - case VZ: - y.MountType = ptr.Of(VIRTIOFS) - case QEMU: - y.MountType = ptr.Of(NINEP) - if _, ok := mountTypesUnsupported[NINEP]; ok { - // Use REVSSHFS if the instance does not support 9p - y.MountType = ptr.Of(REVSSHFS) - } else if isExistingInstanceDir(instDir) && !versionutil.GreaterEqual(existingLimaVersion, "1.0.0") { - // Use REVSSHFS if the instance was created with Lima prior to v1.0 - y.MountType = ptr.Of(REVSSHFS) - } - default: - y.MountType = ptr.Of(REVSSHFS) - } - } - - if _, ok := mountTypesUnsupported[*y.MountType]; ok { - // We cannot return an error here, but Validate() will return it. - logrus.Warnf("Unsupported mount type: %q", *y.MountType) - } if y.MountInotify == nil { y.MountInotify = d.MountInotify @@ -683,7 +636,7 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) { // Combine all mounts; highest priority entry determines writable status. // Only works for exact matches; does not normalize case or resolve symlinks. - mounts := make([]Mount, 0, len(d.Mounts)+len(y.Mounts)+len(o.Mounts)) + mounts := make([]limatype.Mount, 0, len(d.Mounts)+len(y.Mounts)+len(o.Mounts)) location := make(map[string]int) for _, mount := range slices.Concat(d.Mounts, y.Mounts, o.Mounts) { if out, err := executeHostTemplate(mount.Location, instDir, y.Param); err == nil { @@ -756,9 +709,6 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) { if mount.NineP.Msize == nil { mounts[i].NineP.Msize = ptr.Of(Default9pMsize) } - if mount.Virtiofs.QueueSize == nil && *y.VMType == QEMU && *y.MountType == VIRTIOFS { - mounts[i].Virtiofs.QueueSize = ptr.Of(DefaultVirtiofsQueueSize) - } if mount.Writable == nil { mount.Writable = ptr.Of(false) } @@ -820,7 +770,7 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) { y.CACertificates.Files = unique(slices.Concat(d.CACertificates.Files, y.CACertificates.Files, o.CACertificates.Files)) y.CACertificates.Certs = unique(slices.Concat(d.CACertificates.Certs, y.CACertificates.Certs, o.CACertificates.Certs)) - if runtime.GOOS == "darwin" && IsNativeArch(AARCH64) { + if runtime.GOOS == "darwin" && IsNativeArch(limatype.AARCH64) { if y.Rosetta.Enabled == nil { y.Rosetta.Enabled = d.Rosetta.Enabled } @@ -867,7 +817,22 @@ func FillDefault(y, d, o *LimaYAML, filePath string, warn bool) { fixUpForPlainMode(y) } -func fixUpForPlainMode(y *LimaYAML) { +// ExistingLimaVersion returns empty if the instance was created with Lima prior to v0.20, +func ExistingLimaVersion(instDir string) string { + if !IsExistingInstanceDir(instDir) { + return version.Version + } else { + limaVersionFile := filepath.Join(instDir, filenames.LimaVersion) + if b, err := os.ReadFile(limaVersionFile); err == nil { + return strings.TrimSpace(string(b)) + } else if !errors.Is(err, os.ErrNotExist) { + logrus.WithError(err).Warnf("Failed to read %q", limaVersionFile) + } + } + return version.Version +} + +func fixUpForPlainMode(y *limatype.LimaYAML) { if !*y.Plain { return } @@ -880,7 +845,7 @@ func fixUpForPlainMode(y *LimaYAML) { y.TimeZone = ptr.Of("") } -func executeGuestTemplate(format, instDir string, user User, param map[string]string) (bytes.Buffer, error) { +func executeGuestTemplate(format, instDir string, user limatype.User, param map[string]string) (bytes.Buffer, error) { tmpl, err := template.New("").Parse(format) if err == nil { name := filepath.Base(instDir) @@ -932,9 +897,9 @@ func executeHostTemplate(format, instDir string, param map[string]string) (bytes return bytes.Buffer{}, err } -func FillPortForwardDefaults(rule *PortForward, instDir string, user User, param map[string]string) { +func FillPortForwardDefaults(rule *limatype.PortForward, instDir string, user limatype.User, param map[string]string) { if rule.Proto == "" { - rule.Proto = ProtoTCP + rule.Proto = limatype.ProtoTCP } if rule.GuestIP == nil { if rule.GuestIPMustBeZero { @@ -982,7 +947,7 @@ func FillPortForwardDefaults(rule *PortForward, instDir string, user User, param } } -func FillCopyToHostDefaults(rule *CopyToHost, instDir string, user User, param map[string]string) { +func FillCopyToHostDefaults(rule *limatype.CopyToHost, instDir string, user limatype.User, param map[string]string) { if rule.GuestFile != "" { if out, err := executeGuestTemplate(rule.GuestFile, instDir, user, param); err == nil { rule.GuestFile = out.String() @@ -999,72 +964,7 @@ func FillCopyToHostDefaults(rule *CopyToHost, instDir string, user User, param m } } -func NewOS(osname string) OS { - switch osname { - case "linux": - return LINUX - default: - logrus.Warnf("Unknown os: %s", osname) - return osname - } -} - -func goarm() int { - if runtime.GOOS != "linux" { - return 0 - } - if runtime.GOARCH != "arm" { - return 0 - } - if cpu.ARM.HasVFPv3 { - return 7 - } - if cpu.ARM.HasVFP { - return 6 - } - return 5 // default -} - -func NewArch(arch string) Arch { - switch arch { - case "amd64": - return X8664 - case "arm64": - return AARCH64 - case "arm": - arm := goarm() - if arm == 7 { - return ARMV7L - } - logrus.Warnf("Unknown arm: %d", arm) - return arch - case "ppc64le": - return PPC64LE - case "riscv64": - return RISCV64 - case "s390x": - return S390X - default: - logrus.Warnf("Unknown arch: %s", arch) - return arch - } -} - -func NewVMType(driver string) VMType { - switch driver { - case "vz": - return VZ - case "qemu": - return QEMU - case "wsl2": - return WSL2 - default: - logrus.Warnf("Unknown driver: %s", driver) - return driver - } -} - -func isExistingInstanceDir(dir string) bool { +func IsExistingInstanceDir(dir string) bool { // existence of "lima.yaml" does not signify existence of the instance, // because the file is created during the initialization of the instance. for _, f := range []string{ @@ -1079,97 +979,16 @@ func isExistingInstanceDir(dir string) bool { return false } -func ResolveVMType(y, d, o *LimaYAML, filePath string) VMType { - // Check if the VMType is explicitly specified - for i, f := range []*LimaYAML{o, y, d} { - if f.VMType != nil && *f.VMType != "" && *f.VMType != "default" { - logrus.Debugf("ResolveVMType: resolved VMType %q (explicitly specified in []*LimaYAML{o,y,d}[%d])", *f.VMType, i) - return NewVMType(*f.VMType) - } - } - - // If this is an existing instance, guess the VMType from the contents of the instance directory. - if dir, basename := filepath.Split(filePath); dir != "" && basename == filenames.LimaYAML && isExistingInstanceDir(dir) { - if runtime.GOOS == "darwin" { - vzIdentifier := filepath.Join(dir, filenames.VzIdentifier) // since Lima v0.14 - if _, err := os.Lstat(vzIdentifier); !errors.Is(err, os.ErrNotExist) { - logrus.Debugf("ResolveVMType: resolved VMType %q (existing instance, with %q)", VZ, vzIdentifier) - return VZ - } - logrus.Debugf("ResolveVMType: resolved VMType %q (existing instance, without %q)", QEMU, vzIdentifier) - return QEMU - } - logrus.Debugf("ResolveVMType: resolved VMType %q (existing instance)", QEMU) - return QEMU - } - - // Resolve the best type, depending on GOOS - switch runtime.GOOS { - case "darwin": - macOSProductVersion, err := osutil.ProductVersion() - if err != nil { - logrus.WithError(err).Warn("Failed to get macOS product version") - logrus.Debugf("ResolveVMType: resolved VMType %q (default for unknown version of macOS)", QEMU) - return QEMU - } - // Virtualization.framework in macOS prior to 13.5 could not boot Linux kernel v6.2 on Intel - // https://github.com/lima-vm/lima/issues/1577 - if macOSProductVersion.LessThan(*semver.New("13.5.0")) { - logrus.Debugf("ResolveVMType: resolved VMType %q (default for macOS prior to 13.5)", QEMU) - return QEMU - } - // Use QEMU if the config depends on QEMU - for i, f := range []*LimaYAML{o, y, d} { - if f.Arch != nil && !IsNativeArch(*f.Arch) { - logrus.Debugf("ResolveVMType: resolved VMType %q (non-native arch=%q is specified in []*LimaYAML{o,y,d}[%d])", QEMU, *f.Arch, i) - return QEMU - } - if ResolveArch(f.Arch) == X8664 && f.Firmware.LegacyBIOS != nil && *f.Firmware.LegacyBIOS { - logrus.Debugf("ResolveVMType: resolved VMType %q (firmware.legacyBIOS is specified in []*LimaYAML{o,y,d}[%d], on x86_64)", QEMU, i) - return QEMU - } - if f.MountType != nil && *f.MountType == NINEP { - logrus.Debugf("ResolveVMType: resolved VMType %q (mountType=%q is specified in []*LimaYAML{o,y,d}[%d])", QEMU, NINEP, i) - return QEMU - } - if f.Audio.Device != nil { - switch *f.Audio.Device { - case "", "none", "default", "vz": - // NOP - default: - logrus.Debugf("ResolveVMType: resolved VMType %q (audio.device=%q is specified in []*LimaYAML{o,y,d}[%d])", QEMU, *f.Audio.Device, i) - return QEMU - } - } - if f.Video.Display != nil { - switch *f.Video.Display { - case "", "none", "default", "vz": - // NOP - default: - logrus.Debugf("ResolveVMType: resolved VMType %q (video.display=%q is specified in []*LimaYAML{o,y,d}[%d])", QEMU, *f.Video.Display, i) - return QEMU - } - } - } - // Use VZ if the config is compatible with VZ - logrus.Debugf("ResolveVMType: resolved VMType %q (default for macOS 13.5 and later)", VZ) - return VZ - default: - logrus.Debugf("ResolveVMType: resolved VMType %q (default for GOOS=%q)", QEMU, runtime.GOOS) - return QEMU - } -} - -func ResolveOS(s *string) OS { +func ResolveOS(s *string) limatype.OS { if s == nil || *s == "" || *s == "default" { - return NewOS("linux") + return limatype.NewOS("linux") } return *s } -func ResolveArch(s *string) Arch { +func ResolveArch(s *string) limatype.Arch { if s == nil || *s == "" || *s == "default" { - return NewArch(runtime.GOARCH) + return limatype.NewArch(runtime.GOARCH) } return *s } @@ -1224,13 +1043,13 @@ func HasHostCPU() bool { return false } -func IsNativeArch(arch Arch) bool { - nativeX8664 := arch == X8664 && runtime.GOARCH == "amd64" - nativeAARCH64 := arch == AARCH64 && runtime.GOARCH == "arm64" - nativeARMV7L := arch == ARMV7L && runtime.GOARCH == "arm" && goarm() == 7 - nativePPC64LE := arch == PPC64LE && runtime.GOARCH == "ppc64le" - nativeRISCV64 := arch == RISCV64 && runtime.GOARCH == "riscv64" - nativeS390X := arch == S390X && runtime.GOARCH == "s390x" +func IsNativeArch(arch limatype.Arch) bool { + nativeX8664 := arch == limatype.X8664 && runtime.GOARCH == "amd64" + nativeAARCH64 := arch == limatype.AARCH64 && runtime.GOARCH == "arm64" + nativeARMV7L := arch == limatype.ARMV7L && runtime.GOARCH == "arm" && limatype.Goarm() == 7 + nativePPC64LE := arch == limatype.PPC64LE && runtime.GOARCH == "ppc64le" + nativeRISCV64 := arch == limatype.RISCV64 && runtime.GOARCH == "riscv64" + nativeS390X := arch == limatype.S390X && runtime.GOARCH == "s390x" return nativeX8664 || nativeAARCH64 || nativeARMV7L || nativePPC64LE || nativeRISCV64 || nativeS390X } diff --git a/pkg/limayaml/defaults_test.go b/pkg/limayaml/defaults_test.go index 1b9998e6dbb..5bbb424448e 100644 --- a/pkg/limayaml/defaults_test.go +++ b/pkg/limayaml/defaults_test.go @@ -20,43 +20,42 @@ import ( "gotest.tools/v3/assert" "github.com/lima-vm/lima/v2/pkg/ioutilx" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/osutil" "github.com/lima-vm/lima/v2/pkg/ptr" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" - "github.com/lima-vm/lima/v2/pkg/store/filenames" ) func TestFillDefault(t *testing.T) { logrus.SetLevel(logrus.DebugLevel) - var d, y, o LimaYAML - - defaultVMType := ResolveVMType(&y, &d, &o, "") + var d, y, o limatype.LimaYAML opts := []cmp.Option{ // Consider nil slices and empty slices to be identical cmpopts.EquateEmpty(), } - var arch Arch + var arch limatype.Arch switch runtime.GOARCH { case "amd64": - arch = X8664 + arch = limatype.X8664 case "arm64": - arch = AARCH64 + arch = limatype.AARCH64 case "arm": if runtime.GOOS != "linux" { t.Skipf("unsupported GOOS: %s", runtime.GOOS) } - if arm := goarm(); arm < 7 { + if arm := limatype.Goarm(); arm < 7 { t.Skipf("unsupported GOARM: %d", arm) } - arch = ARMV7L + arch = limatype.ARMV7L case "ppc64le": - arch = PPC64LE + arch = limatype.PPC64LE case "riscv64": - arch = RISCV64 + arch = limatype.RISCV64 case "s390x": - arch = S390X + arch = limatype.S390X default: t.Skipf("unknown GOARCH: %s", runtime.GOARCH) } @@ -75,21 +74,20 @@ func TestFillDefault(t *testing.T) { filePath := filepath.Join(instDir, filenames.LimaYAML) // Builtin default values - builtin := LimaYAML{ - VMType: &defaultVMType, - OS: ptr.Of(LINUX), + builtin := limatype.LimaYAML{ + OS: ptr.Of(limatype.LINUX), Arch: ptr.Of(arch), CPUs: ptr.Of(defaultCPUs()), Memory: ptr.Of(defaultMemoryAsString()), Disk: ptr.Of(defaultDiskSizeAsString()), GuestInstallPrefix: ptr.Of(defaultGuestInstallPrefix()), UpgradePackages: ptr.Of(false), - Containerd: Containerd{ + Containerd: limatype.Containerd{ System: ptr.Of(false), User: ptr.Of(true), Archives: defaultContainerdArchives(), }, - SSH: SSH{ + SSH: limatype.SSH{ LocalPort: ptr.Of(0), LoadDotSSHPubKeys: ptr.Of(false), ForwardAgent: ptr.Of(false), @@ -97,29 +95,26 @@ func TestFillDefault(t *testing.T) { ForwardX11Trusted: ptr.Of(false), }, TimeZone: ptr.Of(hostTimeZone()), - Firmware: Firmware{ + Firmware: limatype.Firmware{ LegacyBIOS: ptr.Of(false), }, - Audio: Audio{ + Audio: limatype.Audio{ Device: ptr.Of(""), }, - Video: Video{ + Video: limatype.Video{ Display: ptr.Of("none"), - VNC: VNCOptions{ - Display: ptr.Of("127.0.0.1:0,to=9"), - }, }, - HostResolver: HostResolver{ + HostResolver: limatype.HostResolver{ Enabled: ptr.Of(true), IPv6: ptr.Of(false), }, PropagateProxyEnv: ptr.Of(true), - CACertificates: CACertificates{ + CACertificates: limatype.CACertificates{ RemoveDefaults: ptr.Of(false), }, NestedVirtualization: ptr.Of(false), Plain: ptr.Of(false), - User: User{ + User: limatype.User{ Name: ptr.Of(user.Username), Comment: ptr.Of(user.Name), Home: ptr.Of(user.HomeDir), @@ -128,12 +123,12 @@ func TestFillDefault(t *testing.T) { }, } - defaultPortForward := PortForward{ + defaultPortForward := limatype.PortForward{ GuestIP: IPv4loopback1, GuestPortRange: [2]int{1, 65535}, HostIP: IPv4loopback1, HostPortRange: [2]int{1, 65535}, - Proto: ProtoTCP, + Proto: limatype.ProtoTCP, Reverse: false, } @@ -142,31 +137,31 @@ func TestFillDefault(t *testing.T) { // All these slices and maps are empty in "builtin". Add minimal entries here to see that // their values are retained and defaults for their fields are applied correctly. - y = LimaYAML{ - HostResolver: HostResolver{ + y = limatype.LimaYAML{ + HostResolver: limatype.HostResolver{ Hosts: map[string]string{ "MY.Host": "host.lima.internal", }, }, - Mounts: []Mount{ + Mounts: []limatype.Mount{ //nolint:usetesting // We need the OS temp directory name here; it is not used to create temp files for testing {Location: filepath.Clean(os.TempDir())}, {Location: filepath.Clean("{{.Dir}}/{{.Param.ONE}}"), MountPoint: ptr.Of("/mnt/{{.Param.ONE}}")}, }, - MountType: ptr.Of(NINEP), - Provision: []Provision{ + MountType: ptr.Of(limatype.NINEP), + Provision: []limatype.Provision{ {Script: "#!/bin/true # {{.Param.ONE}}"}, }, - Probes: []Probe{ + Probes: []limatype.Probe{ {Script: "#!/bin/false # {{.Param.ONE}}"}, }, - Networks: []Network{ + Networks: []limatype.Network{ {Lima: "shared"}, }, DNS: []net.IP{ net.ParseIP("1.0.1.0"), }, - PortForwards: []PortForward{ + PortForwards: []limatype.PortForward{ {}, {GuestPort: 80}, {GuestPort: 8080, HostPort: 8888}, @@ -175,7 +170,7 @@ func TestFillDefault(t *testing.T) { HostSocket: "{{.Home}} | {{.Dir}} | {{.Name}} | {{.UID}} | {{.User}} | {{.Param.ONE}}", }, }, - CopyToHost: []CopyToHost{ + CopyToHost: []limatype.CopyToHost{ { GuestFile: "{{.Home}} | {{.UID}} | {{.User}} | {{.Param.ONE}}", HostFile: "{{.Home}} | {{.Dir}} | {{.Name}} | {{.UID}} | {{.User}} | {{.Param.ONE}}", @@ -187,38 +182,18 @@ func TestFillDefault(t *testing.T) { Param: map[string]string{ "ONE": "Eins", }, - CACertificates: CACertificates{ + CACertificates: limatype.CACertificates{ Files: []string{"ca.crt"}, Certs: []string{ "-----BEGIN CERTIFICATE-----\nYOUR-ORGS-TRUSTED-CA-CERT\n-----END CERTIFICATE-----\n", }, }, TimeZone: ptr.Of("Antarctica/Troll"), - Firmware: Firmware{ - LegacyBIOS: ptr.Of(false), - Images: []FileWithVMType{ - { - File: File{ - Location: "https://gitlab.com/kraxel/qemu/-/raw/704f7cad5105246822686f65765ab92045f71a3b/pc-bios/edk2-aarch64-code.fd.bz2", - Arch: AARCH64, - Digest: "sha256:a5fc228623891297f2d82e22ea56ec57cde93fea5ec01abf543e4ed5cacaf277", - }, - VMType: QEMU, - }, - { - File: File{ - Location: "https://github.com/AkihiroSuda/qemu/raw/704f7cad5105246822686f65765ab92045f71a3b/pc-bios/edk2-aarch64-code.fd.bz2", - Arch: AARCH64, - Digest: "sha256:a5fc228623891297f2d82e22ea56ec57cde93fea5ec01abf543e4ed5cacaf277", - }, - VMType: QEMU, - }, - }, - }, } expect := builtin - expect.VMType = ptr.Of(QEMU) // due to NINEP + // VMType should remain nil when not explicitly set (will be resolved by ValidateVMType later) + expect.VMType = nil expect.HostResolver.Hosts = map[string]string{ "MY.Host": "host.lima.internal", } @@ -253,16 +228,16 @@ func TestFillDefault(t *testing.T) { expect.Mounts[1].NineP.Cache = ptr.Of(Default9pCacheForRO) expect.Mounts[1].Virtiofs.QueueSize = nil - expect.MountType = ptr.Of(NINEP) + expect.MountType = ptr.Of(limatype.NINEP) expect.MountInotify = ptr.Of(false) expect.Provision = slices.Clone(y.Provision) - expect.Provision[0].Mode = ProvisionModeSystem + expect.Provision[0].Mode = limatype.ProvisionModeSystem expect.Provision[0].Script = "#!/bin/true # Eins" expect.Probes = slices.Clone(y.Probes) - expect.Probes[0].Mode = ProbeModeReadiness + expect.Probes[0].Mode = limatype.ProbeModeReadiness expect.Probes[0].Description = "user probe 1/1" expect.Probes[0].Script = "#!/bin/false # Eins" @@ -272,13 +247,13 @@ func TestFillDefault(t *testing.T) { expect.Networks[0].Metric = ptr.Of(uint32(100)) expect.DNS = slices.Clone(y.DNS) - expect.PortForwards = []PortForward{ + expect.PortForwards = []limatype.PortForward{ defaultPortForward, defaultPortForward, defaultPortForward, defaultPortForward, } - expect.CopyToHost = []CopyToHost{ + expect.CopyToHost = []limatype.CopyToHost{ {}, } @@ -303,7 +278,7 @@ func TestFillDefault(t *testing.T) { expect.Param = y.Param - expect.CACertificates = CACertificates{ + expect.CACertificates = limatype.CACertificates{ RemoveDefaults: ptr.Of(false), Files: []string{"ca.crt"}, Certs: []string{ @@ -312,17 +287,21 @@ func TestFillDefault(t *testing.T) { } expect.TimeZone = y.TimeZone - expect.Firmware = y.Firmware - expect.Firmware.Images = slices.Clone(y.Firmware.Images) + // Set firmware expectations to match what FillDefault actually does + // FillDefault uses the builtin default values, which include LegacyBIOS: ptr.Of(false) + expect.Firmware = limatype.Firmware{ + LegacyBIOS: ptr.Of(false), // This matches what FillDefault actually sets + Images: nil, + } - expect.Rosetta = Rosetta{ + expect.Rosetta = limatype.Rosetta{ Enabled: ptr.Of(false), BinFmt: ptr.Of(false), } expect.NestedVirtualization = ptr.Of(false) - FillDefault(&y, &LimaYAML{}, &LimaYAML{}, filePath, false) + FillDefault(&y, &limatype.LimaYAML{}, &limatype.LimaYAML{}, filePath, false) assert.DeepEqual(t, &y, &expect, opts...) filledDefaults := y @@ -334,26 +313,26 @@ func TestFillDefault(t *testing.T) { // Calling filepath.Abs() to add a drive letter on Windows varLog, _ := filepath.Abs("/var/log") - d = LimaYAML{ - VMType: ptr.Of("vz"), + d = limatype.LimaYAML{ + // Remove driver-specific VMType from defaults test OS: ptr.Of("unknown"), Arch: ptr.Of("unknown"), CPUs: ptr.Of(7), Memory: ptr.Of("5GiB"), Disk: ptr.Of("105GiB"), - AdditionalDisks: []Disk{ + AdditionalDisks: []limatype.Disk{ {Name: "data"}, }, GuestInstallPrefix: ptr.Of("/opt"), UpgradePackages: ptr.Of(true), - Containerd: Containerd{ + Containerd: limatype.Containerd{ System: ptr.Of(true), User: ptr.Of(false), - Archives: []File{ + Archives: []limatype.File{ {Location: "/tmp/nerdctl.tgz"}, }, }, - SSH: SSH{ + SSH: limatype.SSH{ LocalPort: ptr.Of(888), LoadDotSSHPubKeys: ptr.Of(false), ForwardAgent: ptr.Of(true), @@ -361,27 +340,18 @@ func TestFillDefault(t *testing.T) { ForwardX11Trusted: ptr.Of(false), }, TimeZone: ptr.Of("Zulu"), - Firmware: Firmware{ + Firmware: limatype.Firmware{ LegacyBIOS: ptr.Of(true), - Images: []FileWithVMType{ - { - File: File{ - Location: "/dummy", - Arch: X8664, - }, - }, - }, + // Remove driver-specific firmware images from defaults }, - Audio: Audio{ + Audio: limatype.Audio{ Device: ptr.Of("coreaudio"), }, - Video: Video{ + Video: limatype.Video{ Display: ptr.Of("cocoa"), - VNC: VNCOptions{ - Display: ptr.Of("none"), - }, + // Remove driver-specific VNC configuration }, - HostResolver: HostResolver{ + HostResolver: limatype.HostResolver{ Enabled: ptr.Of(false), IPv6: ptr.Of(true), Hosts: map[string]string{ @@ -390,26 +360,26 @@ func TestFillDefault(t *testing.T) { }, PropagateProxyEnv: ptr.Of(false), - Mounts: []Mount{ + Mounts: []limatype.Mount{ { Location: varLog, Writable: ptr.Of(false), }, }, - Provision: []Provision{ + Provision: []limatype.Provision{ { Script: "#!/bin/true", - Mode: ProvisionModeUser, + Mode: limatype.ProvisionModeUser, }, }, - Probes: []Probe{ + Probes: []limatype.Probe{ { Script: "#!/bin/false", - Mode: ProbeModeReadiness, + Mode: limatype.ProbeModeReadiness, Description: "User Probe", }, }, - Networks: []Network{ + Networks: []limatype.Network{ { MACAddress: "11:22:33:44:55:66", Interface: "def0", @@ -419,16 +389,16 @@ func TestFillDefault(t *testing.T) { DNS: []net.IP{ net.ParseIP("1.1.1.1"), }, - PortForwards: []PortForward{{ + PortForwards: []limatype.PortForward{{ GuestIP: IPv4loopback1, GuestPort: 80, GuestPortRange: [2]int{80, 80}, HostIP: IPv4loopback1, HostPort: 80, HostPortRange: [2]int{80, 80}, - Proto: ProtoTCP, + Proto: limatype.ProtoTCP, }}, - CopyToHost: []CopyToHost{{}}, + CopyToHost: []limatype.CopyToHost{{}}, Env: map[string]string{ "ONE": "one", "TWO": "two", @@ -437,18 +407,18 @@ func TestFillDefault(t *testing.T) { "ONE": "one", "TWO": "two", }, - CACertificates: CACertificates{ + CACertificates: limatype.CACertificates{ RemoveDefaults: ptr.Of(true), Certs: []string{ "-----BEGIN CERTIFICATE-----\nYOUR-ORGS-TRUSTED-CA-CERT\n-----END CERTIFICATE-----\n", }, }, - Rosetta: Rosetta{ + Rosetta: limatype.Rosetta{ Enabled: ptr.Of(true), BinFmt: ptr.Of(true), }, NestedVirtualization: ptr.Of(true), - User: User{ + User: limatype.User{ Name: ptr.Of("xxx"), Comment: ptr.Of("Foo Bar"), Home: ptr.Of("/tmp"), @@ -458,6 +428,8 @@ func TestFillDefault(t *testing.T) { } expect = d + // VMType should remain nil when not explicitly set + expect.VMType = nil // Also verify that archive arch is filled in expect.Containerd.Archives = slices.Clone(d.Containerd.Archives) expect.Containerd.Archives[0].Arch = *d.Arch @@ -480,88 +452,89 @@ func TestFillDefault(t *testing.T) { expect.HostResolver.Hosts = map[string]string{ "default": d.HostResolver.Hosts["default"], } - expect.MountType = ptr.Of(VIRTIOFS) + // Remove driver-specific mount type from defaults test + expect.MountType = nil expect.MountInotify = ptr.Of(false) expect.CACertificates.RemoveDefaults = ptr.Of(true) expect.CACertificates.Certs = []string{ "-----BEGIN CERTIFICATE-----\nYOUR-ORGS-TRUSTED-CA-CERT\n-----END CERTIFICATE-----\n", } - if runtime.GOOS == "darwin" && IsNativeArch(AARCH64) { - expect.Rosetta = Rosetta{ + if runtime.GOOS == "darwin" && IsNativeArch(limatype.AARCH64) { + expect.Rosetta = limatype.Rosetta{ Enabled: ptr.Of(true), BinFmt: ptr.Of(true), } } else { - expect.Rosetta = Rosetta{ + expect.Rosetta = limatype.Rosetta{ Enabled: ptr.Of(false), BinFmt: ptr.Of(true), } } expect.Plain = ptr.Of(false) - y = LimaYAML{} - FillDefault(&y, &d, &LimaYAML{}, filePath, false) + y = limatype.LimaYAML{} + FillDefault(&y, &d, &limatype.LimaYAML{}, filePath, false) assert.DeepEqual(t, &y, &expect, opts...) - dExpect := expect + dExpected := expect // ------------------------------------------------------------------------------------ // User-provided defaults should not override user-provided config values y = filledDefaults y.DNS = []net.IP{net.ParseIP("8.8.8.8")} - y.AdditionalDisks = []Disk{{Name: "overridden"}} + y.AdditionalDisks = []limatype.Disk{{Name: "overridden"}} y.User.Home = ptr.Of("/root") expect = y - expect.Provision = slices.Concat(y.Provision, dExpect.Provision) - expect.Probes = slices.Concat(y.Probes, dExpect.Probes) - expect.PortForwards = slices.Concat(y.PortForwards, dExpect.PortForwards) - expect.CopyToHost = slices.Concat(y.CopyToHost, dExpect.CopyToHost) - expect.Containerd.Archives = slices.Concat(y.Containerd.Archives, dExpect.Containerd.Archives) + expect.Provision = slices.Concat(y.Provision, dExpected.Provision) + expect.Probes = slices.Concat(y.Probes, dExpected.Probes) + expect.PortForwards = slices.Concat(y.PortForwards, dExpected.PortForwards) + expect.CopyToHost = slices.Concat(y.CopyToHost, dExpected.CopyToHost) + expect.Containerd.Archives = slices.Concat(y.Containerd.Archives, dExpected.Containerd.Archives) expect.Containerd.Archives[2].Arch = *expect.Arch - expect.AdditionalDisks = slices.Concat(y.AdditionalDisks, dExpect.AdditionalDisks) - expect.Firmware.Images = slices.Concat(y.Firmware.Images, dExpect.Firmware.Images) + expect.AdditionalDisks = slices.Concat(y.AdditionalDisks, dExpected.AdditionalDisks) + expect.Firmware.Images = slices.Concat(y.Firmware.Images, dExpected.Firmware.Images) // Mounts and Networks start with lowest priority first, so higher priority entries can overwrite - expect.Mounts = slices.Concat(dExpect.Mounts, y.Mounts) - expect.Networks = slices.Concat(dExpect.Networks, y.Networks) + expect.Mounts = slices.Concat(dExpected.Mounts, y.Mounts) + expect.Networks = slices.Concat(dExpected.Networks, y.Networks) - expect.HostResolver.Hosts["default"] = dExpect.HostResolver.Hosts["default"] + expect.HostResolver.Hosts["default"] = dExpected.HostResolver.Hosts["default"] - // dExpect.DNS will be ignored, and not appended to y.DNS + // dExpected.DNS will be ignored, and not appended to y.DNS - // "TWO" does not exist in filledDefaults.Env, so is set from dExpect.Env - expect.Env["TWO"] = dExpect.Env["TWO"] + // "TWO" does not exist in filledDefaults.Env, so is set from dExpected.Env + expect.Env["TWO"] = dExpected.Env["TWO"] - expect.Param["TWO"] = dExpect.Param["TWO"] + expect.Param["TWO"] = dExpected.Param["TWO"] - t.Logf("d.vmType=%q, y.vmType=%q, expect.vmType=%q", *d.VMType, *y.VMType, *expect.VMType) + t.Logf("d.vmType=%v, y.vmType=%v, expect.vmType=%v", d.VMType, y.VMType, expect.VMType) - FillDefault(&y, &d, &LimaYAML{}, filePath, false) + FillDefault(&y, &d, &limatype.LimaYAML{}, filePath, false) assert.DeepEqual(t, &y, &expect, opts...) // ------------------------------------------------------------------------------------ // User-provided overrides should override user-provided config settings - o = LimaYAML{ - VMType: ptr.Of("qemu"), - OS: ptr.Of(LINUX), + o = limatype.LimaYAML{ + // Remove driver-specific VMType from override test + OS: ptr.Of(limatype.LINUX), Arch: ptr.Of(arch), CPUs: ptr.Of(12), Memory: ptr.Of("7GiB"), Disk: ptr.Of("117GiB"), - AdditionalDisks: []Disk{ + AdditionalDisks: []limatype.Disk{ {Name: "test"}, }, GuestInstallPrefix: ptr.Of("/usr"), UpgradePackages: ptr.Of(true), - Containerd: Containerd{ + Containerd: limatype.Containerd{ System: ptr.Of(true), User: ptr.Of(false), - Archives: []File{ + Archives: []limatype.File{ { Arch: arch, Location: "/tmp/nerdctl.tgz", @@ -569,7 +542,7 @@ func TestFillDefault(t *testing.T) { }, }, }, - SSH: SSH{ + SSH: limatype.SSH{ LocalPort: ptr.Of(4433), LoadDotSSHPubKeys: ptr.Of(true), ForwardAgent: ptr.Of(true), @@ -577,19 +550,17 @@ func TestFillDefault(t *testing.T) { ForwardX11Trusted: ptr.Of(false), }, TimeZone: ptr.Of("Universal"), - Firmware: Firmware{ + Firmware: limatype.Firmware{ LegacyBIOS: ptr.Of(true), }, - Audio: Audio{ + Audio: limatype.Audio{ Device: ptr.Of("coreaudio"), }, - Video: Video{ + Video: limatype.Video{ Display: ptr.Of("cocoa"), - VNC: VNCOptions{ - Display: ptr.Of("none"), - }, + // Remove driver-specific VNC configuration }, - HostResolver: HostResolver{ + HostResolver: limatype.HostResolver{ Enabled: ptr.Of(false), IPv6: ptr.Of(false), Hosts: map[string]string{ @@ -598,40 +569,40 @@ func TestFillDefault(t *testing.T) { }, PropagateProxyEnv: ptr.Of(false), - Mounts: []Mount{ + Mounts: []limatype.Mount{ { Location: varLog, Writable: ptr.Of(true), - SSHFS: SSHFS{ + SSHFS: limatype.SSHFS{ Cache: ptr.Of(false), FollowSymlinks: ptr.Of(true), }, - NineP: NineP{ + NineP: limatype.NineP{ SecurityModel: ptr.Of("mapped-file"), ProtocolVersion: ptr.Of("9p2000"), Msize: ptr.Of("8KiB"), Cache: ptr.Of("none"), }, - Virtiofs: Virtiofs{ + Virtiofs: limatype.Virtiofs{ QueueSize: ptr.Of(2048), }, }, }, MountInotify: ptr.Of(true), - Provision: []Provision{ + Provision: []limatype.Provision{ { Script: "#!/bin/true", - Mode: ProvisionModeSystem, + Mode: limatype.ProvisionModeSystem, }, }, - Probes: []Probe{ + Probes: []limatype.Probe{ { Script: "#!/bin/false", - Mode: ProbeModeReadiness, + Mode: limatype.ProbeModeReadiness, Description: "Another Probe", }, }, - Networks: []Network{ + Networks: []limatype.Network{ { Lima: "shared", MACAddress: "10:20:30:40:50:60", @@ -646,16 +617,16 @@ func TestFillDefault(t *testing.T) { DNS: []net.IP{ net.ParseIP("2.2.2.2"), }, - PortForwards: []PortForward{{ + PortForwards: []limatype.PortForward{{ GuestIP: IPv4loopback1, GuestPort: 88, GuestPortRange: [2]int{88, 88}, HostIP: IPv4loopback1, HostPort: 8080, HostPortRange: [2]int{8080, 8080}, - Proto: ProtoTCP, + Proto: limatype.ProtoTCP, }}, - CopyToHost: []CopyToHost{{}}, + CopyToHost: []limatype.CopyToHost{{}}, Env: map[string]string{ "TWO": "deux", "THREE": "trois", @@ -664,15 +635,15 @@ func TestFillDefault(t *testing.T) { "TWO": "deux", "THREE": "trois", }, - CACertificates: CACertificates{ + CACertificates: limatype.CACertificates{ RemoveDefaults: ptr.Of(true), }, - Rosetta: Rosetta{ + Rosetta: limatype.Rosetta{ Enabled: ptr.Of(false), BinFmt: ptr.Of(false), }, NestedVirtualization: ptr.Of(false), - User: User{ + User: limatype.User{ Name: ptr.Of("foo"), Comment: ptr.Of("foo bar baz"), Home: ptr.Of("/override"), @@ -685,20 +656,20 @@ func TestFillDefault(t *testing.T) { expect = o - expect.Provision = slices.Concat(o.Provision, y.Provision, dExpect.Provision) - expect.Probes = slices.Concat(o.Probes, y.Probes, dExpect.Probes) - expect.PortForwards = slices.Concat(o.PortForwards, y.PortForwards, dExpect.PortForwards) - expect.CopyToHost = slices.Concat(o.CopyToHost, y.CopyToHost, dExpect.CopyToHost) - expect.Containerd.Archives = slices.Concat(o.Containerd.Archives, y.Containerd.Archives, dExpect.Containerd.Archives) + expect.Provision = slices.Concat(o.Provision, y.Provision, dExpected.Provision) + expect.Probes = slices.Concat(o.Probes, y.Probes, dExpected.Probes) + expect.PortForwards = slices.Concat(o.PortForwards, y.PortForwards, dExpected.PortForwards) + expect.CopyToHost = slices.Concat(o.CopyToHost, y.CopyToHost, dExpected.CopyToHost) + expect.Containerd.Archives = slices.Concat(o.Containerd.Archives, y.Containerd.Archives, dExpected.Containerd.Archives) expect.Containerd.Archives[3].Arch = *expect.Arch - expect.AdditionalDisks = slices.Concat(o.AdditionalDisks, y.AdditionalDisks, dExpect.AdditionalDisks) - expect.Firmware.Images = slices.Concat(o.Firmware.Images, y.Firmware.Images, dExpect.Firmware.Images) + expect.AdditionalDisks = slices.Concat(o.AdditionalDisks, y.AdditionalDisks, dExpected.AdditionalDisks) + expect.Firmware.Images = slices.Concat(o.Firmware.Images, y.Firmware.Images, dExpected.Firmware.Images) - expect.HostResolver.Hosts["default"] = dExpect.HostResolver.Hosts["default"] - expect.HostResolver.Hosts["MY.Host"] = dExpect.HostResolver.Hosts["host.lima.internal"] + expect.HostResolver.Hosts["default"] = dExpected.HostResolver.Hosts["default"] + expect.HostResolver.Hosts["MY.Host"] = dExpected.HostResolver.Hosts["host.lima.internal"] - // o.Mounts just makes dExpect.Mounts[0] writable because the Location matches - expect.Mounts = slices.Concat(dExpect.Mounts, y.Mounts) + // o.Mounts just makes dExpected.Mounts[0] writable because the Location matches + expect.Mounts = slices.Concat(dExpected.Mounts, y.Mounts) expect.Mounts[0].Writable = ptr.Of(true) expect.Mounts[0].SSHFS.Cache = ptr.Of(false) expect.Mounts[0].SSHFS.FollowSymlinks = ptr.Of(true) @@ -708,11 +679,11 @@ func TestFillDefault(t *testing.T) { expect.Mounts[0].NineP.Cache = ptr.Of("none") expect.Mounts[0].Virtiofs.QueueSize = ptr.Of(2048) - expect.MountType = ptr.Of(NINEP) + expect.MountType = ptr.Of(limatype.NINEP) expect.MountInotify = ptr.Of(true) - // o.Networks[1] is overriding the dExpect.Networks[0].Lima entry for the "def0" interface - expect.Networks = slices.Concat(dExpect.Networks, y.Networks, []Network{o.Networks[0]}) + // o.Networks[1] is overriding the dExpected.Networks[0].Lima entry for the "def0" interface + expect.Networks = slices.Concat(dExpected.Networks, y.Networks, []limatype.Network{o.Networks[0]}) expect.Networks[0].Lima = o.Networks[1].Lima // Only highest prio DNS are retained @@ -729,7 +700,7 @@ func TestFillDefault(t *testing.T) { "-----BEGIN CERTIFICATE-----\nYOUR-ORGS-TRUSTED-CA-CERT\n-----END CERTIFICATE-----\n", } - expect.Rosetta = Rosetta{ + expect.Rosetta = limatype.Rosetta{ Enabled: ptr.Of(false), BinFmt: ptr.Of(false), } diff --git a/pkg/limayaml/limayaml_test.go b/pkg/limayaml/limayaml_test.go index 39c929eaf3e..79e7554aca6 100644 --- a/pkg/limayaml/limayaml_test.go +++ b/pkg/limayaml/limayaml_test.go @@ -8,6 +8,7 @@ import ( "os" "testing" + "github.com/lima-vm/lima/v2/pkg/limatype" "gotest.tools/v3/assert" ) @@ -20,7 +21,7 @@ func dumpJSON(t *testing.T, d any) string { const emptyYAML = "{}\n" func TestEmptyYAML(t *testing.T) { - var y LimaYAML + var y limatype.LimaYAML t.Log(dumpJSON(t, y)) b, err := Marshal(&y, false) assert.NilError(t, err) @@ -32,7 +33,7 @@ const defaultYAML = "{}\n" func TestDefaultYAML(t *testing.T) { bytes, err := os.ReadFile("default.yaml") assert.NilError(t, err) - var y LimaYAML + var y limatype.LimaYAML err = Unmarshal(bytes, &y, "") assert.NilError(t, err) y.Images = nil // remove default images diff --git a/pkg/limayaml/load.go b/pkg/limayaml/load.go index f6d7914b6b9..ffce6578356 100644 --- a/pkg/limayaml/load.go +++ b/pkg/limayaml/load.go @@ -11,26 +11,27 @@ import ( "github.com/sirupsen/logrus" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" - "github.com/lima-vm/lima/v2/pkg/store/filenames" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" ) // Load loads the yaml and fulfills unspecified fields with the default values. // // Load does not validate. Use Validate for validation. -func Load(b []byte, filePath string) (*LimaYAML, error) { +func Load(b []byte, filePath string) (*limatype.LimaYAML, error) { return load(b, filePath, false) } // LoadWithWarnings will call FillDefaults with warnings enabled (e.g. when // the username is not valid on Linux and must be replaced by "Lima"). // It is called when creating or editing an instance. -func LoadWithWarnings(b []byte, filePath string) (*LimaYAML, error) { +func LoadWithWarnings(b []byte, filePath string) (*limatype.LimaYAML, error) { return load(b, filePath, true) } -func load(b []byte, filePath string, warn bool) (*LimaYAML, error) { - var y, d, o LimaYAML +func load(b []byte, filePath string, warn bool) (*limatype.LimaYAML, error) { + var y, d, o limatype.LimaYAML if err := Unmarshal(b, &y, fmt.Sprintf("main file %q", filePath)); err != nil { return nil, err @@ -68,5 +69,6 @@ func load(b []byte, filePath string, warn bool) (*LimaYAML, error) { } FillDefault(&y, &d, &o, filePath, warn) + return &y, nil } diff --git a/pkg/limayaml/marshal.go b/pkg/limayaml/marshal.go index efa8553087f..a43821f3e13 100644 --- a/pkg/limayaml/marshal.go +++ b/pkg/limayaml/marshal.go @@ -9,6 +9,7 @@ import ( "github.com/goccy/go-yaml" "github.com/sirupsen/logrus" + "github.com/lima-vm/lima/v2/pkg/limatype" "github.com/lima-vm/lima/v2/pkg/yqutil" ) @@ -18,7 +19,7 @@ const ( ) // Marshal the struct as a YAML document, optionally as a stream. -func Marshal(y *LimaYAML, stream bool) ([]byte, error) { +func Marshal(y *limatype.LimaYAML, stream bool) ([]byte, error) { b, err := yaml.Marshal(y) if err != nil { return nil, err @@ -30,40 +31,40 @@ func Marshal(y *LimaYAML, stream bool) ([]byte, error) { return b, nil } -func unmarshalDisk(dst *Disk, b []byte) error { +func unmarshalDisk(dst *limatype.Disk, b []byte) error { var s string if err := yaml.Unmarshal(b, &s); err == nil { - *dst = Disk{Name: s} + *dst = limatype.Disk{Name: s} return nil } return yaml.Unmarshal(b, dst) } // unmarshalBaseTemplates unmarshalls `base` which is either a string or a list of Locators. -func unmarshalBaseTemplates(dst *BaseTemplates, b []byte) error { +func unmarshalBaseTemplates(dst *limatype.BaseTemplates, b []byte) error { var s string if err := yaml.Unmarshal(b, &s); err == nil { - *dst = BaseTemplates{LocatorWithDigest{URL: s}} + *dst = limatype.BaseTemplates{limatype.LocatorWithDigest{URL: s}} return nil } - return yaml.UnmarshalWithOptions(b, dst, yaml.CustomUnmarshaler[LocatorWithDigest](unmarshalLocatorWithDigest)) + return yaml.UnmarshalWithOptions(b, dst, yaml.CustomUnmarshaler[limatype.LocatorWithDigest](unmarshalLocatorWithDigest)) } // unmarshalLocator unmarshalls a locator which is either a string or a Locator struct. -func unmarshalLocatorWithDigest(dst *LocatorWithDigest, b []byte) error { +func unmarshalLocatorWithDigest(dst *limatype.LocatorWithDigest, b []byte) error { var s string if err := yaml.Unmarshal(b, &s); err == nil { - *dst = LocatorWithDigest{URL: s} + *dst = limatype.LocatorWithDigest{URL: s} return nil } return yaml.Unmarshal(b, dst) } -func Unmarshal(data []byte, y *LimaYAML, comment string) error { +func Unmarshal(data []byte, y *limatype.LimaYAML, comment string) error { opts := []yaml.DecodeOption{ - yaml.CustomUnmarshaler[BaseTemplates](unmarshalBaseTemplates), - yaml.CustomUnmarshaler[Disk](unmarshalDisk), - yaml.CustomUnmarshaler[LocatorWithDigest](unmarshalLocatorWithDigest), + yaml.CustomUnmarshaler[limatype.BaseTemplates](unmarshalBaseTemplates), + yaml.CustomUnmarshaler[limatype.Disk](unmarshalDisk), + yaml.CustomUnmarshaler[limatype.LocatorWithDigest](unmarshalLocatorWithDigest), } if err := yaml.UnmarshalWithOptions(data, y, opts...); err != nil { return fmt.Errorf("failed to unmarshal YAML (%s): %w", comment, err) @@ -75,7 +76,7 @@ func Unmarshal(data []byte, y *LimaYAML, comment string) error { } // Finally log a warning if the YAML file violates the "strict" rules opts = append(opts, yaml.Strict()) - var ignore LimaYAML + var ignore limatype.LimaYAML if err := yaml.UnmarshalWithOptions(data, &ignore, opts...); err != nil { logrus.WithField("comment", comment).WithError(err).Warn("Non-strict YAML detected; please check for typos") } diff --git a/pkg/limayaml/marshal_test.go b/pkg/limayaml/marshal_test.go index 7e778ae0602..4ad8f3ab03e 100644 --- a/pkg/limayaml/marshal_test.go +++ b/pkg/limayaml/marshal_test.go @@ -8,17 +8,18 @@ import ( "gotest.tools/v3/assert" + "github.com/lima-vm/lima/v2/pkg/limatype" "github.com/lima-vm/lima/v2/pkg/ptr" ) func TestMarshalEmpty(t *testing.T) { - _, err := Marshal(&LimaYAML{}, false) + _, err := Marshal(&limatype.LimaYAML{}, false) assert.NilError(t, err) } func TestMarshalTilde(t *testing.T) { - y := LimaYAML{ - Mounts: []Mount{ + y := limatype.LimaYAML{ + Mounts: []limatype.Mount{ {Location: "~", Writable: ptr.Of(false)}, {Location: "/tmp/lima", Writable: ptr.Of(true)}, {Location: "null"}, diff --git a/pkg/limayaml/validate.go b/pkg/limayaml/validate.go index e09f56b3a1b..1a60c27d1a7 100644 --- a/pkg/limayaml/validate.go +++ b/pkg/limayaml/validate.go @@ -21,7 +21,9 @@ import ( "github.com/docker/go-units" "github.com/sirupsen/logrus" + "github.com/lima-vm/lima/v2/pkg/driverutil" "github.com/lima-vm/lima/v2/pkg/identifiers" + "github.com/lima-vm/lima/v2/pkg/limatype" "github.com/lima-vm/lima/v2/pkg/localpathutil" "github.com/lima-vm/lima/v2/pkg/networks" "github.com/lima-vm/lima/v2/pkg/osutil" @@ -29,7 +31,7 @@ import ( "github.com/lima-vm/lima/v2/pkg/version/versionutil" ) -func Validate(y *LimaYAML, warn bool) error { +func Validate(y *limatype.LimaYAML, warn bool) error { var errs error if len(y.Base) > 0 { @@ -54,24 +56,12 @@ func Validate(y *LimaYAML, warn bool) error { } } switch *y.OS { - case LINUX: + case limatype.LINUX: default: - errs = errors.Join(errs, fmt.Errorf("field `os` must be %q; got %q", LINUX, *y.OS)) + errs = errors.Join(errs, fmt.Errorf("field `os` must be %q; got %q", limatype.LINUX, *y.OS)) } - if !slices.Contains(ArchTypes, *y.Arch) { - errs = errors.Join(errs, fmt.Errorf("field `arch` must be one of %v; got %q", ArchTypes, *y.Arch)) - } - switch *y.VMType { - case QEMU: - // NOP - case WSL2: - // NOP - case VZ: - if !IsNativeArch(*y.Arch) { - errs = errors.Join(errs, fmt.Errorf("field `arch` must be %q for VZ; got %q", NewArch(runtime.GOARCH), *y.Arch)) - } - default: - errs = errors.Join(errs, fmt.Errorf("field `vmType` must be %q, %q, %q; got %q", QEMU, VZ, WSL2, *y.VMType)) + if !slices.Contains(limatype.ArchTypes, *y.Arch) { + errs = errors.Join(errs, fmt.Errorf("field `arch` must be one of %v; got %q", limatype.ArchTypes, *y.Arch)) } if len(y.Images) == 0 { @@ -165,14 +155,17 @@ func Validate(y *LimaYAML, warn bool) error { } } - switch *y.MountType { - case REVSSHFS, NINEP, VIRTIOFS, WSLMount: - default: - errs = errors.Join(errs, fmt.Errorf("field `mountType` must be %q or %q or %q, or %q, got %q", REVSSHFS, NINEP, VIRTIOFS, WSLMount, *y.MountType)) - } + if y.MountType != nil { + switch *y.MountType { + case limatype.REVSSHFS, limatype.NINEP, limatype.VIRTIOFS, limatype.WSLMount: + default: + errs = errors.Join(errs, fmt.Errorf("field `mountType` must be %q or %q or %q, or %q, got %q", limatype.REVSSHFS, limatype.NINEP, limatype.VIRTIOFS, limatype.WSLMount, *y.MountType)) + } + + if slices.Contains(y.MountTypesUnsupported, *y.MountType) { + errs = errors.Join(errs, fmt.Errorf("field `mountType` must not be one of %v (`mountTypesUnsupported`), got %q", y.MountTypesUnsupported, *y.MountType)) + } - if slices.Contains(y.MountTypesUnsupported, *y.MountType) { - errs = errors.Join(errs, fmt.Errorf("field `mountType` must not be one of %v (`mountTypesUnsupported`), got %q", y.MountTypesUnsupported, *y.MountType)) } if warn && runtime.GOOS != "linux" { @@ -195,55 +188,55 @@ func Validate(y *LimaYAML, warn bool) error { } } switch p.Mode { - case ProvisionModeSystem, ProvisionModeUser, ProvisionModeBoot, ProvisionModeData, ProvisionModeDependency, ProvisionModeAnsible: + case limatype.ProvisionModeSystem, limatype.ProvisionModeUser, limatype.ProvisionModeBoot, limatype.ProvisionModeData, limatype.ProvisionModeDependency, limatype.ProvisionModeAnsible: default: errs = errors.Join(errs, fmt.Errorf("field `provision[%d].mode` must one of %q, %q, %q, %q, %q, or %q", - i, ProvisionModeSystem, ProvisionModeUser, ProvisionModeBoot, ProvisionModeData, ProvisionModeDependency, ProvisionModeAnsible)) + i, limatype.ProvisionModeSystem, limatype.ProvisionModeUser, limatype.ProvisionModeBoot, limatype.ProvisionModeData, limatype.ProvisionModeDependency, limatype.ProvisionModeAnsible)) } - if p.Mode != ProvisionModeDependency && p.SkipDefaultDependencyResolution != nil { + if p.Mode != limatype.ProvisionModeDependency && p.SkipDefaultDependencyResolution != nil { errs = errors.Join(errs, fmt.Errorf("field `provision[%d].mode` cannot set skipDefaultDependencyResolution, only valid on scripts of type %q", - i, ProvisionModeDependency)) + i, limatype.ProvisionModeDependency)) } // This can lead to fatal Panic if p.Path is nil, better to return an error here - if p.Mode == ProvisionModeData { + if p.Mode == limatype.ProvisionModeData { if p.Path == nil { - errs = errors.Join(errs, fmt.Errorf("field `provision[%d].path` must not be empty when mode is %q", i, ProvisionModeData)) + errs = errors.Join(errs, fmt.Errorf("field `provision[%d].path` must not be empty when mode is %q", i, limatype.ProvisionModeData)) return errs } if !path.IsAbs(*p.Path) { errs = errors.Join(errs, fmt.Errorf("field `provision[%d].path` must be an absolute path", i)) } if p.Content == nil { - errs = errors.Join(errs, fmt.Errorf("field `provision[%d].content` must not be empty when mode is %q", i, ProvisionModeData)) + errs = errors.Join(errs, fmt.Errorf("field `provision[%d].content` must not be empty when mode is %q", i, limatype.ProvisionModeData)) } // FillDefaults makes sure that p.Permissions is not nil if _, err := strconv.ParseInt(*p.Permissions, 8, 64); err != nil { errs = errors.Join(errs, fmt.Errorf("field `provision[%d].permissions` must be an octal number: %w", i, err)) } } else { - if p.Script == "" && p.Mode != ProvisionModeAnsible { + if p.Script == "" && p.Mode != limatype.ProvisionModeAnsible { errs = errors.Join(errs, fmt.Errorf("field `provision[%d].script` must not be empty", i)) } if p.Content != nil { - errs = errors.Join(errs, fmt.Errorf("field `provision[%d].content` can only be set when mode is %q", i, ProvisionModeData)) + errs = errors.Join(errs, fmt.Errorf("field `provision[%d].content` can only be set when mode is %q", i, limatype.ProvisionModeData)) } if p.Overwrite != nil { - errs = errors.Join(errs, fmt.Errorf("field `provision[%d].overwrite` can only be set when mode is %q", i, ProvisionModeData)) + errs = errors.Join(errs, fmt.Errorf("field `provision[%d].overwrite` can only be set when mode is %q", i, limatype.ProvisionModeData)) } if p.Owner != nil { - errs = errors.Join(errs, fmt.Errorf("field `provision[%d].owner` can only be set when mode is %q", i, ProvisionModeData)) + errs = errors.Join(errs, fmt.Errorf("field `provision[%d].owner` can only be set when mode is %q", i, limatype.ProvisionModeData)) } if p.Path != nil { - errs = errors.Join(errs, fmt.Errorf("field `provision[%d].path` can only be set when mode is %q", i, ProvisionModeData)) + errs = errors.Join(errs, fmt.Errorf("field `provision[%d].path` can only be set when mode is %q", i, limatype.ProvisionModeData)) } if p.Permissions != nil { - errs = errors.Join(errs, fmt.Errorf("field `provision[%d].permissions` can only be set when mode is %q", i, ProvisionModeData)) + errs = errors.Join(errs, fmt.Errorf("field `provision[%d].permissions` can only be set when mode is %q", i, limatype.ProvisionModeData)) } } if p.Playbook != "" { - if p.Mode != ProvisionModeAnsible { - errs = errors.Join(errs, fmt.Errorf("field `provision[%d].playbook can only be set when mode is %q", i, ProvisionModeAnsible)) + if p.Mode != limatype.ProvisionModeAnsible { + errs = errors.Join(errs, fmt.Errorf("field `provision[%d].playbook can only be set when mode is %q", i, limatype.ProvisionModeAnsible)) } if p.Script != "" { errs = errors.Join(errs, fmt.Errorf("field `provision[%d].script must be empty if playbook is set", i)) @@ -252,7 +245,7 @@ func Validate(y *LimaYAML, warn bool) error { if _, err := os.Stat(playbook); err != nil { errs = errors.Join(errs, fmt.Errorf("field `provision[%d].playbook` refers to an inaccessible path: %q: %w", i, playbook, err)) } - logrus.Warnf("provision mode %q is deprecated, use `ansible-playbook %q` instead", ProvisionModeAnsible, playbook) + logrus.Warnf("provision mode %q is deprecated, use `ansible-playbook %q` instead", limatype.ProvisionModeAnsible, playbook) } if strings.Contains(p.Script, "LIMA_CIDATA") { logrus.Warn("provisioning scripts should not reference the LIMA_CIDATA variables") @@ -283,9 +276,9 @@ func Validate(y *LimaYAML, warn bool) error { errs = errors.Join(errs, fmt.Errorf("field `probe[%d].script` must start with a '#!' line", i)) } switch p.Mode { - case ProbeModeReadiness: + case limatype.ProbeModeReadiness: default: - errs = errors.Join(errs, fmt.Errorf("field `probe[%d].mode` can only be %q", i, ProbeModeReadiness)) + errs = errors.Join(errs, fmt.Errorf("field `probe[%d].mode` can only be %q", i, limatype.ProbeModeReadiness)) } } for i, rule := range y.PortForwards { @@ -356,9 +349,9 @@ func Validate(y *LimaYAML, warn bool) error { field, osutil.UnixPathMax, len(rule.HostSocket))) } switch rule.Proto { - case ProtoTCP, ProtoUDP, ProtoAny: + case limatype.ProtoTCP, limatype.ProtoUDP, limatype.ProtoAny: default: - errs = errors.Join(errs, fmt.Errorf("field `%s.proto` must be %q, %q, or %q", field, ProtoTCP, ProtoUDP, ProtoAny)) + errs = errors.Join(errs, fmt.Errorf("field `%s.proto` must be %q, %q, or %q", field, limatype.ProtoTCP, limatype.ProtoUDP, limatype.ProtoAny)) } if rule.Reverse && rule.GuestSocket == "" { errs = errors.Join(errs, fmt.Errorf("field `%s.reverse` must be %t", field, false)) @@ -413,7 +406,7 @@ func Validate(y *LimaYAML, warn bool) error { return errs } -func validateFileObject(f File, fieldName string) error { +func validateFileObject(f limatype.File, fieldName string) error { var errs error if !strings.Contains(f.Location, "://") { if _, err := localpathutil.Expand(f.Location); err != nil { @@ -421,8 +414,8 @@ func validateFileObject(f File, fieldName string) error { } // f.Location does NOT need to be accessible, so we do NOT check os.Stat(f.Location) } - if !slices.Contains(ArchTypes, f.Arch) { - errs = errors.Join(errs, fmt.Errorf("field `arch` must be one of %v; got %q", ArchTypes, f.Arch)) + if !slices.Contains(limatype.ArchTypes, f.Arch) { + errs = errors.Join(errs, fmt.Errorf("field `arch` must be one of %v; got %q", limatype.ArchTypes, f.Arch)) } if f.Digest != "" { if err := f.Digest.Validate(); err != nil { @@ -432,7 +425,7 @@ func validateFileObject(f File, fieldName string) error { return errs } -func validateNetwork(y *LimaYAML) error { +func validateNetwork(y *limatype.LimaYAML) error { var errs error interfaceName := make(map[string]int) for i, nw := range y.Networks { @@ -469,8 +462,8 @@ func validateNetwork(y *LimaYAML) error { errs = errors.Join(errs, fmt.Errorf("field `%s.socket` %q points to a non-socket file", field, nw.Socket)) } case nw.VZNAT != nil && *nw.VZNAT: - if y.VMType == nil || *y.VMType != VZ { - errs = errors.Join(errs, fmt.Errorf("field `%s.vzNAT` requires `vmType` to be %q", field, VZ)) + if y.VMType == nil || *y.VMType != limatype.VZ { + errs = errors.Join(errs, fmt.Errorf("field `%s.vzNAT` requires `vmType` to be %q", field, limatype.VZ)) } if nw.Lima != "" { errs = errors.Join(errs, fmt.Errorf("field `%s.vzNAT` and field `%s.lima` are mutually exclusive", field, field)) @@ -511,7 +504,7 @@ func validateNetwork(y *LimaYAML) error { // validateParamIsUsed checks if the keys in the `param` field are used in any script, probe, copyToHost, or portForward. // It should be called before the `y` parameter is passed to FillDefault() that execute template. -func validateParamIsUsed(y *LimaYAML) error { +func validateParamIsUsed(y *limatype.LimaYAML) error { for key := range y.Param { re, err := regexp.Compile(`{{[^}]*\.Param\.` + key + `[^}]*}}|\bPARAM_` + key + `\b`) if err != nil { @@ -583,12 +576,12 @@ func validatePort(field string, port int) error { return nil } -func warnExperimental(y *LimaYAML) { - if *y.MountType == VIRTIOFS && runtime.GOOS == "linux" { +func warnExperimental(y *limatype.LimaYAML) { + if *y.MountType == limatype.VIRTIOFS && runtime.GOOS == "linux" { logrus.Warn("`mountType: virtiofs` on Linux is experimental") } switch *y.Arch { - case RISCV64, ARMV7L, S390X, PPC64LE: + case limatype.RISCV64, limatype.ARMV7L, limatype.S390X, limatype.PPC64LE: logrus.Warnf("`arch: %s ` is experimental", *y.Arch) } if y.Video.Display != nil && strings.Contains(*y.Video.Display, "vnc") { @@ -605,36 +598,44 @@ func warnExperimental(y *LimaYAML) { // ValidateAgainstLatestConfig validates the values between the latest YAML and the updated(New) YAML. // This validates configuration rules that disallow certain changes, such as shrinking the disk. func ValidateAgainstLatestConfig(yNew, yLatest []byte) error { - var n LimaYAML + var n limatype.LimaYAML + var errs error // Load the latest YAML and fill in defaults l, err := LoadWithWarnings(yLatest, "") if err != nil { - return err + errs = errors.Join(errs, err) + } + if err := driverutil.ResolveVMType(l, ""); err != nil { + errs = errors.Join(errs, fmt.Errorf("failed to accept config for %q: %w", "", err)) } if err := Unmarshal(yNew, &n, "Unmarshal new YAML bytes"); err != nil { - return err + errs = errors.Join(errs, err) + } + + if (n.VMType != nil && l.VMType != nil) && *n.VMType != *l.VMType { + errs = errors.Join(errs, fmt.Errorf("cannot change VMType from %v to %v, please create a new instance with the desired VMType", *l.VMType, *n.VMType)) } // Handle editing the template without a disk value if n.Disk == nil || l.Disk == nil { - return nil + return errs } // Disk value must be provided, as it is required when creating an instance. nDisk, err := units.RAMInBytes(*n.Disk) if err != nil { - return err + errs = errors.Join(errs, err) } lDisk, err := units.RAMInBytes(*l.Disk) if err != nil { - return err + errs = errors.Join(errs, err) } // Reject shrinking disk if nDisk < lDisk { - return fmt.Errorf("field `disk`: shrinking the disk (%v --> %v) is not supported", *l.Disk, *n.Disk) + errs = errors.Join(errs, fmt.Errorf("field `disk`: shrinking the disk (%v --> %v) is not supported", *l.Disk, *n.Disk)) } - return nil + return errs } diff --git a/pkg/limayaml/validate_test.go b/pkg/limayaml/validate_test.go index 5c9d696a0de..1fa839d8788 100644 --- a/pkg/limayaml/validate_test.go +++ b/pkg/limayaml/validate_test.go @@ -4,11 +4,12 @@ package limayaml import ( - "errors" + "fmt" "testing" "gotest.tools/v3/assert" + "github.com/lima-vm/lima/v2/pkg/limatype" "github.com/lima-vm/lima/v2/pkg/version" ) @@ -312,7 +313,6 @@ func TestValidateMultipleErrors(t *testing.T) { yamlWithMultipleErrors := ` os: windows arch: unsupported_arch -vmType: invalid_type portForwards: - guestPort: 22 hostPort: 2222 @@ -328,10 +328,10 @@ provision: y, err := Load([]byte(yamlWithMultipleErrors), "multiple-errors.yaml") assert.NilError(t, err) err = Validate(y, false) + t.Logf("Validation errors: %v", err) assert.Error(t, err, "field `os` must be \"Linux\"; got \"windows\"\n"+ "field `arch` must be one of [x86_64 aarch64 armv7l ppc64le riscv64 s390x]; got \"unsupported_arch\"\n"+ - "field `vmType` must be \"qemu\", \"vz\", \"wsl2\"; got \"invalid_type\"\n"+ "field `images` must be set\n"+ "field `provision[0].mode` must one of \"system\", \"user\", \"boot\", \"data\", \"dependency\", or \"ansible\"\n"+ "field `provision[1].path` must not be empty when mode is \"data\"") @@ -342,49 +342,51 @@ func TestValidateAgainstLatestConfig(t *testing.T) { name string yNew string yLatest string - wantErr error + wantErr string }{ { name: "Valid disk size unchanged", yNew: `disk: 100GiB`, yLatest: `disk: 100GiB`, + wantErr: fmt.Sprintf("failed to accept config for \"\": vmType %q is not a registered driver", limatype.DefaultDriver()), }, { name: "Valid disk size increased", yNew: `disk: 200GiB`, yLatest: `disk: 100GiB`, + wantErr: fmt.Sprintf("failed to accept config for \"\": vmType %q is not a registered driver", limatype.DefaultDriver()), }, { name: "No disk field in both YAMLs", yNew: ``, yLatest: ``, + wantErr: fmt.Sprintf("failed to accept config for \"\": vmType %q is not a registered driver", limatype.DefaultDriver()), }, { name: "No disk field in new YAMLs", yNew: ``, yLatest: `disk: 100GiB`, + wantErr: fmt.Sprintf("failed to accept config for \"\": vmType %q is not a registered driver", limatype.DefaultDriver()), }, { name: "No disk field in latest YAMLs", yNew: `disk: 100GiB`, yLatest: ``, + wantErr: fmt.Sprintf("failed to accept config for \"\": vmType %q is not a registered driver", limatype.DefaultDriver()), }, { name: "Disk size shrunk", yNew: `disk: 50GiB`, yLatest: `disk: 100GiB`, - wantErr: errors.New("field `disk`: shrinking the disk (100GiB --> 50GiB) is not supported"), + wantErr: fmt.Sprintf("failed to accept config for \"\": vmType %q is not a registered driver\n", limatype.DefaultDriver()) + + "field `disk`: shrinking the disk (100GiB --> 50GiB) is not supported", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := ValidateAgainstLatestConfig([]byte(tt.yNew), []byte(tt.yLatest)) - if tt.wantErr == nil { - assert.NilError(t, err) - } else { - assert.Error(t, err, tt.wantErr.Error()) - } + assert.Error(t, err, tt.wantErr) }) } } diff --git a/pkg/networks/commands.go b/pkg/networks/commands.go index 98dbb1075fe..abe34650741 100644 --- a/pkg/networks/commands.go +++ b/pkg/networks/commands.go @@ -10,8 +10,8 @@ import ( "os/exec" "path/filepath" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" "github.com/lima-vm/lima/v2/pkg/osutil" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" ) const ( diff --git a/pkg/networks/commands_test.go b/pkg/networks/commands_test.go index 2f9877e633c..4d5667d958b 100644 --- a/pkg/networks/commands_test.go +++ b/pkg/networks/commands_test.go @@ -10,7 +10,7 @@ import ( "gotest.tools/v3/assert" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" ) func TestCheck(t *testing.T) { diff --git a/pkg/networks/config.go b/pkg/networks/config.go index 34b5f2260f3..6cefe46b58e 100644 --- a/pkg/networks/config.go +++ b/pkg/networks/config.go @@ -15,8 +15,8 @@ import ( "github.com/goccy/go-yaml" "github.com/sirupsen/logrus" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" - "github.com/lima-vm/lima/v2/pkg/store/filenames" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/textutil" ) diff --git a/pkg/networks/reconcile/reconcile.go b/pkg/networks/reconcile/reconcile.go index 020e261166a..0b08c153352 100644 --- a/pkg/networks/reconcile/reconcile.go +++ b/pkg/networks/reconcile/reconcile.go @@ -16,11 +16,12 @@ import ( "github.com/sirupsen/logrus" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" "github.com/lima-vm/lima/v2/pkg/networks" "github.com/lima-vm/lima/v2/pkg/networks/usernet" "github.com/lima-vm/lima/v2/pkg/osutil" "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" ) func Reconcile(ctx context.Context, newInst string) error { @@ -39,7 +40,7 @@ func Reconcile(ctx context.Context, newInst string) error { return err } // newInst is about to be started, so its networks should be running - if instance.Status != store.StatusRunning && instName != newInst { + if instance.Status != limatype.StatusRunning && instName != newInst { continue } for _, nw := range instance.Networks { diff --git a/pkg/networks/usernet/client.go b/pkg/networks/usernet/client.go index 53333c2f19a..f556c0a081a 100644 --- a/pkg/networks/usernet/client.go +++ b/pkg/networks/usernet/client.go @@ -18,9 +18,9 @@ import ( "github.com/containers/gvisor-tap-vsock/pkg/types" "github.com/lima-vm/lima/v2/pkg/httpclientutil" + "github.com/lima-vm/lima/v2/pkg/limatype" "github.com/lima-vm/lima/v2/pkg/limayaml" "github.com/lima-vm/lima/v2/pkg/networks/usernet/dnshosts" - "github.com/lima-vm/lima/v2/pkg/store" ) type Client struct { @@ -32,7 +32,7 @@ type Client struct { subnet net.IP } -func (c *Client) ConfigureDriver(ctx context.Context, inst *store.Instance, sshLocalPort int) error { +func (c *Client) ConfigureDriver(ctx context.Context, inst *limatype.Instance, sshLocalPort int) error { macAddress := limayaml.MACAddress(inst.Dir) ipAddress, err := c.ResolveIPAddress(ctx, macAddress) if err != nil { diff --git a/pkg/networks/usernet/config.go b/pkg/networks/usernet/config.go index e8304deea0f..359044947dd 100644 --- a/pkg/networks/usernet/config.go +++ b/pkg/networks/usernet/config.go @@ -10,9 +10,9 @@ import ( "github.com/apparentlymart/go-cidr/cidr" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" "github.com/lima-vm/lima/v2/pkg/networks" "github.com/lima-vm/lima/v2/pkg/osutil" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" ) type SockType = string diff --git a/pkg/networks/usernet/recoincile.go b/pkg/networks/usernet/recoincile.go index 2ce920e3578..5862d8af82a 100644 --- a/pkg/networks/usernet/recoincile.go +++ b/pkg/networks/usernet/recoincile.go @@ -19,9 +19,9 @@ import ( "github.com/sirupsen/logrus" "github.com/lima-vm/lima/v2/pkg/executil" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" "github.com/lima-vm/lima/v2/pkg/lockutil" "github.com/lima-vm/lima/v2/pkg/store" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" ) // Start starts a instance a usernet network with the given name. diff --git a/pkg/portfwd/forward.go b/pkg/portfwd/forward.go index 1dc44dfaff3..d20d9a7e217 100644 --- a/pkg/portfwd/forward.go +++ b/pkg/portfwd/forward.go @@ -12,19 +12,20 @@ import ( "github.com/lima-vm/lima/v2/pkg/guestagent/api" guestagentclient "github.com/lima-vm/lima/v2/pkg/guestagent/api/client" + "github.com/lima-vm/lima/v2/pkg/limatype" "github.com/lima-vm/lima/v2/pkg/limayaml" ) var IPv4loopback1 = limayaml.IPv4loopback1 type Forwarder struct { - rules []limayaml.PortForward + rules []limatype.PortForward ignoreTCP bool ignoreUDP bool closableListeners *ClosableListeners } -func NewPortForwarder(rules []limayaml.PortForward, ignoreTCP, ignoreUDP bool) *Forwarder { +func NewPortForwarder(rules []limatype.PortForward, ignoreTCP, ignoreUDP bool) *Forwarder { return &Forwarder{ rules: rules, ignoreTCP: ignoreTCP, @@ -64,7 +65,7 @@ func (fw *Forwarder) forwardingAddresses(guest *api.IPPort) (hostAddr, guestAddr if rule.GuestSocket != "" { continue } - if rule.Proto != limayaml.ProtoAny && rule.Proto != guest.Protocol { + if rule.Proto != limatype.ProtoAny && rule.Proto != guest.Protocol { continue } if guest.Port < int32(rule.GuestPortRange[0]) || guest.Port > int32(rule.GuestPortRange[1]) { @@ -91,7 +92,7 @@ func (fw *Forwarder) forwardingAddresses(guest *api.IPPort) (hostAddr, guestAddr return "", guest.HostString() } -func hostAddress(rule limayaml.PortForward, guest *api.IPPort) string { +func hostAddress(rule limatype.PortForward, guest *api.IPPort) string { if rule.HostSocket != "" { return rule.HostSocket } diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index 4abd3a54159..66a66726562 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -14,7 +14,7 @@ import ( "gotest.tools/v3/assert" "github.com/lima-vm/lima/v2/pkg/driver" - "github.com/lima-vm/lima/v2/pkg/store" + "github.com/lima-vm/lima/v2/pkg/limatype" ) type mockDriver struct { @@ -28,7 +28,8 @@ func newMockDriver(name string) *mockDriver { var _ driver.Driver = (*mockDriver)(nil) func (m *mockDriver) Validate() error { return nil } -func (m *mockDriver) Initialize(_ context.Context) error { return nil } +func (m *mockDriver) Create(_ context.Context) error { return nil } +func (m *mockDriver) Delete(_ context.Context) error { return nil } func (m *mockDriver) CreateDisk(_ context.Context) error { return nil } func (m *mockDriver) Start(_ context.Context) (chan error, error) { return nil, nil } func (m *mockDriver) Stop(_ context.Context) error { return nil } @@ -44,7 +45,10 @@ func (m *mockDriver) Unregister(_ context.Context) error func (m *mockDriver) ForwardGuestAgent() bool { return false } func (m *mockDriver) GuestAgentConn(_ context.Context) (net.Conn, string, error) { return nil, "", nil } func (m *mockDriver) Info() driver.Info { return driver.Info{DriverName: m.Name} } -func (m *mockDriver) Configure(_ *store.Instance) *driver.ConfiguredDriver { return nil } +func (m *mockDriver) Configure(_ *limatype.Instance) *driver.ConfiguredDriver { return nil } +func (m *mockDriver) AcceptConfig(_ *limatype.LimaYAML, _ string) error { return nil } +func (m *mockDriver) FillConfig(_ *limatype.LimaYAML, _ string) error { return nil } +func (m *mockDriver) InspectStatus(_ context.Context, _ string) string { return "" } func TestRegister(t *testing.T) { BackupRegistry(t) diff --git a/pkg/snapshot/snapshot.go b/pkg/snapshot/snapshot.go index 530363f8d81..e76caa6efdf 100644 --- a/pkg/snapshot/snapshot.go +++ b/pkg/snapshot/snapshot.go @@ -8,10 +8,10 @@ import ( "fmt" "github.com/lima-vm/lima/v2/pkg/driverutil" - "github.com/lima-vm/lima/v2/pkg/store" + "github.com/lima-vm/lima/v2/pkg/limatype" ) -func Del(ctx context.Context, inst *store.Instance, tag string) error { +func Del(ctx context.Context, inst *limatype.Instance, tag string) error { limaDriver, err := driverutil.CreateConfiguredDriver(inst, 0) if err != nil { return fmt.Errorf("failed to create driver instance: %w", err) @@ -20,7 +20,7 @@ func Del(ctx context.Context, inst *store.Instance, tag string) error { return limaDriver.DeleteSnapshot(ctx, tag) } -func Save(ctx context.Context, inst *store.Instance, tag string) error { +func Save(ctx context.Context, inst *limatype.Instance, tag string) error { limaDriver, err := driverutil.CreateConfiguredDriver(inst, 0) if err != nil { return fmt.Errorf("failed to create driver instance: %w", err) @@ -28,7 +28,7 @@ func Save(ctx context.Context, inst *store.Instance, tag string) error { return limaDriver.CreateSnapshot(ctx, tag) } -func Load(ctx context.Context, inst *store.Instance, tag string) error { +func Load(ctx context.Context, inst *limatype.Instance, tag string) error { limaDriver, err := driverutil.CreateConfiguredDriver(inst, 0) if err != nil { return fmt.Errorf("failed to create driver instance: %w", err) @@ -36,7 +36,7 @@ func Load(ctx context.Context, inst *store.Instance, tag string) error { return limaDriver.ApplySnapshot(ctx, tag) } -func List(ctx context.Context, inst *store.Instance) (string, error) { +func List(ctx context.Context, inst *limatype.Instance) (string, error) { limaDriver, err := driverutil.CreateConfiguredDriver(inst, 0) if err != nil { return "", fmt.Errorf("failed to create driver instance: %w", err) diff --git a/pkg/sshutil/sshutil.go b/pkg/sshutil/sshutil.go index 302c9a5035f..a7845df6cf9 100644 --- a/pkg/sshutil/sshutil.go +++ b/pkg/sshutil/sshutil.go @@ -25,10 +25,10 @@ import ( "golang.org/x/sys/cpu" "github.com/lima-vm/lima/v2/pkg/ioutilx" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/lockutil" "github.com/lima-vm/lima/v2/pkg/osutil" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" - "github.com/lima-vm/lima/v2/pkg/store/filenames" ) // Environment variable that allows configuring the command (alias) to execute diff --git a/pkg/store/disk.go b/pkg/store/disk.go index 5df18fbcea5..20a69f91e63 100644 --- a/pkg/store/disk.go +++ b/pkg/store/disk.go @@ -12,7 +12,7 @@ import ( "github.com/lima-vm/go-qcow2reader" - "github.com/lima-vm/lima/v2/pkg/store/filenames" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" ) type Disk struct { diff --git a/pkg/store/fuzz_test.go b/pkg/store/fuzz_test.go index 9d2c4930d10..e56a99d85db 100644 --- a/pkg/store/fuzz_test.go +++ b/pkg/store/fuzz_test.go @@ -10,7 +10,7 @@ import ( "gotest.tools/v3/assert" - "github.com/lima-vm/lima/v2/pkg/store/filenames" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" ) func FuzzLoadYAMLByFilePath(f *testing.F) { diff --git a/pkg/store/instance.go b/pkg/store/instance.go index 0de2c3f69a7..027f02c841c 100644 --- a/pkg/store/instance.go +++ b/pkg/store/instance.go @@ -5,7 +5,6 @@ package store import ( "context" - "encoding/json" "errors" "fmt" "io" @@ -22,64 +21,27 @@ import ( "github.com/docker/go-units" "github.com/sirupsen/logrus" + "github.com/lima-vm/lima/v2/pkg/driverutil" hostagentclient "github.com/lima-vm/lima/v2/pkg/hostagent/api/client" "github.com/lima-vm/lima/v2/pkg/instance/hostname" - "github.com/lima-vm/lima/v2/pkg/limayaml" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" - "github.com/lima-vm/lima/v2/pkg/store/filenames" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/textutil" "github.com/lima-vm/lima/v2/pkg/version/versionutil" ) -type Status = string - -const ( - StatusUnknown Status = "" - StatusUninitialized Status = "Uninitialized" - StatusInstalling Status = "Installing" - StatusBroken Status = "Broken" - StatusStopped Status = "Stopped" - StatusRunning Status = "Running" -) - -type Instance struct { - Name string `json:"name"` - // Hostname, not HostName (corresponds to SSH's naming convention) - Hostname string `json:"hostname"` - Status Status `json:"status"` - Dir string `json:"dir"` - VMType limayaml.VMType `json:"vmType"` - Arch limayaml.Arch `json:"arch"` - CPUType string `json:"cpuType"` - CPUs int `json:"cpus,omitempty"` - Memory int64 `json:"memory,omitempty"` // bytes - Disk int64 `json:"disk,omitempty"` // bytes - Message string `json:"message,omitempty"` - AdditionalDisks []limayaml.Disk `json:"additionalDisks,omitempty"` - Networks []limayaml.Network `json:"network,omitempty"` - SSHLocalPort int `json:"sshLocalPort,omitempty"` - SSHConfigFile string `json:"sshConfigFile,omitempty"` - HostAgentPID int `json:"hostAgentPID,omitempty"` - DriverPID int `json:"driverPID,omitempty"` - Errors []error `json:"errors,omitempty"` - Config *limayaml.LimaYAML `json:"config,omitempty"` - SSHAddress string `json:"sshAddress,omitempty"` - Protected bool `json:"protected"` - LimaVersion string `json:"limaVersion"` - Param map[string]string `json:"param,omitempty"` -} - // Inspect returns err only when the instance does not exist (os.ErrNotExist). // Other errors are returned as *Instance.Errors. -func Inspect(instName string) (*Instance, error) { - inst := &Instance{ +func Inspect(instName string) (*limatype.Instance, error) { + inst := &limatype.Instance{ Name: instName, // TODO: support customizing hostname Hostname: hostname.FromInstName(instName), - Status: StatusUnknown, + Status: limatype.StatusUnknown, } // InstanceDir validates the instName but does not check whether the instance exists - instDir, err := InstanceDir(instName) + instDir, err := dirnames.InstanceDir(instName) if err != nil { return nil, err } @@ -103,7 +65,7 @@ func Inspect(instName string) (*Instance, error) { inst.SSHConfigFile = filepath.Join(instDir, filenames.SSHConfig) inst.HostAgentPID, err = ReadPIDFile(filepath.Join(instDir, filenames.HostAgentPID)) if err != nil { - inst.Status = StatusBroken + inst.Status = limatype.StatusBroken inst.Errors = append(inst.Errors, err) } @@ -111,14 +73,14 @@ func Inspect(instName string) (*Instance, error) { haSock := filepath.Join(instDir, filenames.HostAgentSock) haClient, err := hostagentclient.NewHostAgentClient(haSock) if err != nil { - inst.Status = StatusBroken + inst.Status = limatype.StatusBroken inst.Errors = append(inst.Errors, fmt.Errorf("failed to connect to %q: %w", haSock, err)) } else { ctx, cancel := context.WithTimeout(context.TODO(), 3*time.Second) defer cancel() info, err := haClient.Info(ctx) if err != nil { - inst.Status = StatusBroken + inst.Status = limatype.StatusBroken inst.Errors = append(inst.Errors, fmt.Errorf("failed to get Info from %q: %w", haSock, err)) } else { inst.SSHLocalPort = info.SSHLocalPort @@ -139,7 +101,7 @@ func Inspect(instName string) (*Instance, error) { inst.Networks = y.Networks // 0 out values since not configurable on WSL2 - if inst.VMType == limayaml.WSL2 { + if inst.VMType == limatype.WSL2 { inst.Memory = 0 inst.CPUs = 0 inst.Disk = 0 @@ -155,18 +117,18 @@ func Inspect(instName string) (*Instance, error) { tmpl, err := template.New("format").Parse(y.Message) if err != nil { inst.Errors = append(inst.Errors, fmt.Errorf("message %q is not a valid template: %w", y.Message, err)) - inst.Status = StatusBroken + inst.Status = limatype.StatusBroken } else { data, err := AddGlobalFields(inst) if err != nil { inst.Errors = append(inst.Errors, fmt.Errorf("cannot add global fields to instance data: %w", err)) - inst.Status = StatusBroken + inst.Status = limatype.StatusBroken } else { var message strings.Builder err = tmpl.Execute(&message, data) if err != nil { inst.Errors = append(inst.Errors, fmt.Errorf("cannot execute template %q: %w", y.Message, err)) - inst.Status = StatusBroken + inst.Status = limatype.StatusBroken } else { inst.Message = message.String() } @@ -186,26 +148,47 @@ func Inspect(instName string) (*Instance, error) { return inst, nil } -func inspectStatusWithPIDFiles(instDir string, inst *Instance, y *limayaml.LimaYAML) { +func inspectStatus(instDir string, inst *limatype.Instance, y *limatype.LimaYAML) { + driver, err := driverutil.CreateConfiguredDriver(inst, inst.SSHLocalPort) + if err != nil { + inst.Errors = append(inst.Errors, fmt.Errorf("failed to create driver instance: %w", err)) + inst.Status = limatype.StatusBroken + return + } + + status := driver.InspectStatus(context.Background(), inst.Name) + if status == "" { + inspectStatusWithPIDFiles(instDir, inst, y) + return + } + + inst.Status = status +} + +func GetSSHAddress(_ string) (string, error) { + return "127.0.0.1", nil +} + +func inspectStatusWithPIDFiles(instDir string, inst *limatype.Instance, y *limatype.LimaYAML) { var err error inst.DriverPID, err = ReadPIDFile(filepath.Join(instDir, filenames.PIDFile(*y.VMType))) if err != nil { - inst.Status = StatusBroken + inst.Status = limatype.StatusBroken inst.Errors = append(inst.Errors, err) } - if inst.Status == StatusUnknown { + if inst.Status == limatype.StatusUnknown { switch { case inst.HostAgentPID > 0 && inst.DriverPID > 0: - inst.Status = StatusRunning + inst.Status = limatype.StatusRunning case inst.HostAgentPID == 0 && inst.DriverPID == 0: - inst.Status = StatusStopped + inst.Status = limatype.StatusStopped case inst.HostAgentPID > 0 && inst.DriverPID == 0: inst.Errors = append(inst.Errors, errors.New("host agent is running but driver is not")) - inst.Status = StatusBroken + inst.Status = limatype.StatusBroken default: inst.Errors = append(inst.Errors, fmt.Errorf("%s driver is running but host agent is not", inst.VMType)) - inst.Status = StatusBroken + inst.Status = limatype.StatusBroken } } } @@ -248,7 +231,7 @@ func ReadPIDFile(path string) (int, error) { } type FormatData struct { - Instance + limatype.Instance HostOS string HostArch string LimaHome string @@ -260,13 +243,13 @@ var FormatHelp = "\n" + textutil.IndentString(2, strings.Join(textutil.FuncHelp, "\n")+"\n") -func AddGlobalFields(inst *Instance) (FormatData, error) { +func AddGlobalFields(inst *limatype.Instance) (FormatData, error) { var data FormatData data.Instance = *inst // Add HostOS data.HostOS = runtime.GOOS // Add HostArch - data.HostArch = limayaml.NewArch(runtime.GOARCH) + data.HostArch = limatype.NewArch(runtime.GOARCH) // Add IdentityFile configDir, err := dirnames.LimaConfigDir() if err != nil { @@ -288,7 +271,7 @@ type PrintOptions struct { // PrintInstances prints instances in a requested format to a given io.Writer. // Supported formats are "json", "yaml", "table", or a go template. -func PrintInstances(w io.Writer, instances []*Instance, format string, options *PrintOptions) error { +func PrintInstances(w io.Writer, instances []*limatype.Instance, format string, options *PrintOptions) error { switch format { case "json": format = "{{json .}}" @@ -322,7 +305,7 @@ func PrintInstances(w io.Writer, instances []*Instance, format string, options * columns++ // VMTYPE } // only hide arch if it is the same as the host arch - goarch := limayaml.NewArch(runtime.GOARCH) + goarch := limatype.NewArch(runtime.GOARCH) // can we still fit the remaining columns (6) if width == 0 || (columns+6)*columnWidth > width && !all { hideArch = len(archs) == 1 && instances[0].Arch == goarch @@ -415,62 +398,3 @@ func PrintInstances(w io.Writer, instances []*Instance, format string, options * } return nil } - -// Protect protects the instance to prohibit accidental removal. -// Protect does not return an error even when the instance is already protected. -func (inst *Instance) Protect() error { - protected := filepath.Join(inst.Dir, filenames.Protected) - // TODO: Do an equivalent of `chmod +a "everyone deny delete,delete_child,file_inherit,directory_inherit"` - // https://github.com/lima-vm/lima/issues/1595 - if err := os.WriteFile(protected, nil, 0o400); err != nil { - return err - } - inst.Protected = true - return nil -} - -// Unprotect unprotects the instance. -// Unprotect does not return an error even when the instance is already unprotected. -func (inst *Instance) Unprotect() error { - protected := filepath.Join(inst.Dir, filenames.Protected) - if err := os.RemoveAll(protected); err != nil { - return err - } - inst.Protected = false - return nil -} - -func (inst *Instance) MarshalJSON() ([]byte, error) { - type Alias Instance - errorsAsStrings := make([]string, len(inst.Errors)) - for i, err := range inst.Errors { - if err != nil { - errorsAsStrings[i] = err.Error() - } - } - return json.Marshal(&struct { - *Alias - Errors []string `json:"errors,omitempty"` - }{ - Alias: (*Alias)(inst), - Errors: errorsAsStrings, - }) -} - -func (inst *Instance) UnmarshalJSON(data []byte) error { - type Alias Instance - aux := &struct { - *Alias - Errors []string `json:"errors,omitempty"` - }{ - Alias: (*Alias)(inst), - } - if err := json.Unmarshal(data, &aux); err != nil { - return err - } - inst.Errors = nil - for _, msg := range aux.Errors { - inst.Errors = append(inst.Errors, errors.New(msg)) - } - return nil -} diff --git a/pkg/store/instance_test.go b/pkg/store/instance_test.go index 55b7e57bf2a..28ffcb7aa8d 100644 --- a/pkg/store/instance_test.go +++ b/pkg/store/instance_test.go @@ -13,20 +13,20 @@ import ( "gotest.tools/v3/assert" - "github.com/lima-vm/lima/v2/pkg/limayaml" + "github.com/lima-vm/lima/v2/pkg/limatype" ) const separator = string(filepath.Separator) var ( - vmtype = limayaml.QEMU - goarch = limayaml.NewArch(runtime.GOARCH) + vmtype = limatype.QEMU + goarch = limatype.NewArch(runtime.GOARCH) space = strings.Repeat(" ", len(goarch)-4) ) -var instance = Instance{ +var instance = limatype.Instance{ Name: "foo", - Status: StatusStopped, + Status: limatype.StatusStopped, VMType: vmtype, Arch: goarch, Dir: "dir", @@ -68,7 +68,7 @@ var tableTwo = "NAME STATUS SSH VMTYPE ARCH CPUS M func TestPrintInstanceTable(t *testing.T) { var buf bytes.Buffer - instances := []*Instance{&instance} + instances := []*limatype.Instance{&instance} err := PrintInstances(&buf, instances, "table", nil) assert.NilError(t, err) assert.Equal(t, table, buf.String()) @@ -78,7 +78,7 @@ func TestPrintInstanceTableEmu(t *testing.T) { var buf bytes.Buffer instance1 := instance instance1.Arch = "unknown" - instances := []*Instance{&instance1} + instances := []*limatype.Instance{&instance1} err := PrintInstances(&buf, instances, "table", nil) assert.NilError(t, err) assert.Equal(t, tableEmu, buf.String()) @@ -90,7 +90,7 @@ func TestPrintInstanceTableHome(t *testing.T) { assert.NilError(t, err) instance1 := instance instance1.Dir = filepath.Join(homeDir, "dir") - instances := []*Instance{&instance1} + instances := []*limatype.Instance{&instance1} err = PrintInstances(&buf, instances, "table", nil) assert.NilError(t, err) assert.Equal(t, tableHome, buf.String()) @@ -98,7 +98,7 @@ func TestPrintInstanceTableHome(t *testing.T) { func TestPrintInstanceTable60(t *testing.T) { var buf bytes.Buffer - instances := []*Instance{&instance} + instances := []*limatype.Instance{&instance} options := PrintOptions{TerminalWidth: 60} err := PrintInstances(&buf, instances, "table", &options) assert.NilError(t, err) @@ -107,7 +107,7 @@ func TestPrintInstanceTable60(t *testing.T) { func TestPrintInstanceTable80SameArch(t *testing.T) { var buf bytes.Buffer - instances := []*Instance{&instance} + instances := []*limatype.Instance{&instance} options := PrintOptions{TerminalWidth: 80} err := PrintInstances(&buf, instances, "table", &options) assert.NilError(t, err) @@ -117,8 +117,8 @@ func TestPrintInstanceTable80SameArch(t *testing.T) { func TestPrintInstanceTable80DiffArch(t *testing.T) { var buf bytes.Buffer instance1 := instance - instance1.Arch = limayaml.NewArch("unknown") - instances := []*Instance{&instance1} + instance1.Arch = limatype.NewArch("unknown") + instances := []*limatype.Instance{&instance1} options := PrintOptions{TerminalWidth: 80} err := PrintInstances(&buf, instances, "table", &options) assert.NilError(t, err) @@ -127,7 +127,7 @@ func TestPrintInstanceTable80DiffArch(t *testing.T) { func TestPrintInstanceTable100(t *testing.T) { var buf bytes.Buffer - instances := []*Instance{&instance} + instances := []*limatype.Instance{&instance} options := PrintOptions{TerminalWidth: 100} err := PrintInstances(&buf, instances, "table", &options) assert.NilError(t, err) @@ -136,7 +136,7 @@ func TestPrintInstanceTable100(t *testing.T) { func TestPrintInstanceTableAll(t *testing.T) { var buf bytes.Buffer - instances := []*Instance{&instance} + instances := []*limatype.Instance{&instance} options := PrintOptions{TerminalWidth: 40, AllFields: true} err := PrintInstances(&buf, instances, "table", &options) assert.NilError(t, err) @@ -147,13 +147,13 @@ func TestPrintInstanceTableTwo(t *testing.T) { var buf bytes.Buffer instance1 := instance instance1.Name = "foo" - instance1.VMType = limayaml.QEMU - instance1.Arch = limayaml.X8664 + instance1.VMType = limatype.QEMU + instance1.Arch = limatype.X8664 instance2 := instance instance2.Name = "bar" - instance2.VMType = limayaml.VZ - instance2.Arch = limayaml.AARCH64 - instances := []*Instance{&instance1, &instance2} + instance2.VMType = limatype.VZ + instance2.Arch = limatype.AARCH64 + instances := []*limatype.Instance{&instance1, &instance2} options := PrintOptions{TerminalWidth: 80} err := PrintInstances(&buf, instances, "table", &options) assert.NilError(t, err) diff --git a/pkg/store/instance_unix.go b/pkg/store/instance_unix.go deleted file mode 100644 index 41ded92c368..00000000000 --- a/pkg/store/instance_unix.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !windows - -// SPDX-FileCopyrightText: Copyright The Lima Authors -// SPDX-License-Identifier: Apache-2.0 - -package store - -import "github.com/lima-vm/lima/v2/pkg/limayaml" - -func inspectStatus(instDir string, inst *Instance, y *limayaml.LimaYAML) { - inspectStatusWithPIDFiles(instDir, inst, y) -} - -func GetSSHAddress(_ string) (string, error) { - return "127.0.0.1", nil -} diff --git a/pkg/store/instance_windows.go b/pkg/store/instance_windows.go deleted file mode 100644 index 136d6a85490..00000000000 --- a/pkg/store/instance_windows.go +++ /dev/null @@ -1,120 +0,0 @@ -// SPDX-FileCopyrightText: Copyright The Lima Authors -// SPDX-License-Identifier: Apache-2.0 - -package store - -import ( - "fmt" - "regexp" - "strings" - - "github.com/lima-vm/lima/v2/pkg/executil" - "github.com/lima-vm/lima/v2/pkg/limayaml" -) - -func inspectStatus(instDir string, inst *Instance, y *limayaml.LimaYAML) { - if inst.VMType == limayaml.WSL2 { - status, err := GetWslStatus(inst.Name) - if err != nil { - inst.Status = StatusBroken - inst.Errors = append(inst.Errors, err) - } else { - inst.Status = status - } - - inst.SSHLocalPort = 22 - - if inst.Status == StatusRunning { - sshAddr, err := GetSSHAddress(inst.Name) - if err == nil { - inst.SSHAddress = sshAddr - } else { - inst.Errors = append(inst.Errors, err) - } - } - } else { - inspectStatusWithPIDFiles(instDir, inst, y) - } -} - -// GetWslStatus runs `wsl --list --verbose` and parses its output. -// There are several possible outputs, all listed with their whitespace preserved output below. -// -// (1) Expected output if at least one distro is installed: -// PS > wsl --list --verbose -// -// NAME STATE VERSION -// -// * Ubuntu Stopped 2 -// -// (2) Expected output when no distros are installed, but WSL is configured properly: -// PS > wsl --list --verbose -// Windows Subsystem for Linux has no installed distributions. -// -// Use 'wsl.exe --list --online' to list available distributions -// and 'wsl.exe --install ' to install. -// -// Distributions can also be installed by visiting the Microsoft Store: -// https://aka.ms/wslstore -// Error code: Wsl/WSL_E_DEFAULT_DISTRO_NOT_FOUND -// -// (3) Expected output when no distros are installed, and WSL2 has no kernel installed: -// -// PS > wsl --list --verbose -// Windows Subsystem for Linux has no installed distributions. -// Distributions can be installed by visiting the Microsoft Store: -// https://aka.ms/wslstore -func GetWslStatus(instName string) (string, error) { - distroName := "lima-" + instName - out, err := executil.RunUTF16leCommand([]string{ - "wsl.exe", - "--list", - "--verbose", - }) - if err != nil { - return "", fmt.Errorf("failed to run `wsl --list --verbose`, err: %w (out=%q)", err, out) - } - - if out == "" { - return StatusBroken, fmt.Errorf("failed to read instance state for instance %q, try running `wsl --list --verbose` to debug, err: %w", instName, err) - } - - // Check for edge cases first - if strings.Contains(out, "Windows Subsystem for Linux has no installed distributions.") { - if strings.Contains(out, "Wsl/WSL_E_DEFAULT_DISTRO_NOT_FOUND") { - return StatusBroken, fmt.Errorf( - "failed to read instance state for instance %q because no distro is installed,"+ - "try running `wsl --install -d Ubuntu` and then re-running Lima", instName) - } - return StatusBroken, fmt.Errorf( - "failed to read instance state for instance %q because there is no WSL kernel installed,"+ - "this usually happens when WSL was installed for another user, but never for your user."+ - "Try running `wsl --install -d Ubuntu` and `wsl --update`, and then re-running Lima", instName) - } - - var instState string - wslListColsRegex := regexp.MustCompile(`\s+`) - // wsl --list --verbose may have different headers depending on localization, just split by line - for _, rows := range strings.Split(strings.ReplaceAll(out, "\r\n", "\n"), "\n") { - cols := wslListColsRegex.Split(strings.TrimSpace(rows), -1) - nameIdx := 0 - // '*' indicates default instance - if cols[0] == "*" { - nameIdx = 1 - } - if cols[nameIdx] == distroName { - instState = cols[nameIdx+1] - break - } - } - - if instState == "" { - return StatusUninitialized, nil - } - - return instState, nil -} - -func GetSSHAddress(_ string) (string, error) { - return "127.0.0.1", nil -} diff --git a/pkg/store/store.go b/pkg/store/store.go index 24816d30286..ab1d54d56ad 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -10,10 +10,12 @@ import ( "path/filepath" "strings" + "github.com/lima-vm/lima/v2/pkg/driverutil" "github.com/lima-vm/lima/v2/pkg/identifiers" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" "github.com/lima-vm/lima/v2/pkg/limayaml" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" - "github.com/lima-vm/lima/v2/pkg/store/filenames" ) // Directory returns the LimaDir. @@ -46,19 +48,6 @@ func Validate() error { return nil } -// ValidateInstName checks if the name is a valid instance name. For this it needs to -// be a valid identifier, and not end in .yml or .yaml (case insensitively). -func ValidateInstName(name string) error { - if err := identifiers.Validate(name); err != nil { - return fmt.Errorf("instance name %q is not a valid identifier: %w", name, err) - } - lower := strings.ToLower(name) - if strings.HasSuffix(lower, ".yml") || strings.HasSuffix(lower, ".yaml") { - return fmt.Errorf("instance name %q must not end with .yml or .yaml suffix", name) - } - return nil -} - // Instances returns the names of the instances under LimaDir. func Instances() ([]string, error) { limaDir, err := dirnames.LimaDir() @@ -104,20 +93,6 @@ func Disks() ([]string, error) { return names, nil } -// InstanceDir returns the instance dir. -// InstanceDir does not check whether the instance exists. -func InstanceDir(name string) (string, error) { - if err := ValidateInstName(name); err != nil { - return "", err - } - limaDir, err := dirnames.LimaDir() - if err != nil { - return "", err - } - dir := filepath.Join(limaDir, name) - return dir, nil -} - func DiskDir(name string) (string, error) { if err := identifiers.Validate(name); err != nil { return "", err @@ -131,7 +106,7 @@ func DiskDir(name string) (string, error) { } // LoadYAMLByFilePath loads and validates the yaml. -func LoadYAMLByFilePath(filePath string) (*limayaml.LimaYAML, error) { +func LoadYAMLByFilePath(filePath string) (*limatype.LimaYAML, error) { // We need to use the absolute path because it may be used to determine hostSocket locations. absPath, err := filepath.Abs(filePath) if err != nil { @@ -145,6 +120,9 @@ func LoadYAMLByFilePath(filePath string) (*limayaml.LimaYAML, error) { if err != nil { return nil, err } + if err := driverutil.ResolveVMType(y, filePath); err != nil { + return nil, fmt.Errorf("failed to accept config for %q: %w", filePath, err) + } if err := limayaml.Validate(y, false); err != nil { return nil, err } diff --git a/pkg/store/store_test.go b/pkg/store/store_test.go index 4dfe1eea87b..3a4141372ba 100644 --- a/pkg/store/store_test.go +++ b/pkg/store/store_test.go @@ -6,6 +6,7 @@ package store import ( "testing" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" "gotest.tools/v3/assert" ) @@ -22,7 +23,7 @@ func TestValidateInstName(t *testing.T) { } for _, arg := range instNames { t.Run(arg, func(t *testing.T) { - err := ValidateInstName(arg) + err := dirnames.ValidateInstName(arg) assert.NilError(t, err) }) } @@ -43,7 +44,7 @@ func TestValidateInstName(t *testing.T) { } for _, arg := range invalidIdentifiers { t.Run(arg, func(t *testing.T) { - err := ValidateInstName(arg) + err := dirnames.ValidateInstName(arg) assert.ErrorContains(t, err, "not a valid identifier") }) } @@ -54,7 +55,7 @@ func TestValidateInstName(t *testing.T) { } for _, arg := range yamlNames { t.Run(arg, func(t *testing.T) { - err := ValidateInstName(arg) + err := dirnames.ValidateInstName(arg) assert.ErrorContains(t, err, "must not end with .y") }) } diff --git a/pkg/templatestore/templatestore.go b/pkg/templatestore/templatestore.go index a61eeb2c808..9591a59aa04 100644 --- a/pkg/templatestore/templatestore.go +++ b/pkg/templatestore/templatestore.go @@ -14,7 +14,7 @@ import ( "strings" "unicode" - "github.com/lima-vm/lima/v2/pkg/store/dirnames" + "github.com/lima-vm/lima/v2/pkg/limatype/dirnames" "github.com/lima-vm/lima/v2/pkg/usrlocalsharelima" ) diff --git a/pkg/usrlocalsharelima/usrlocalsharelima.go b/pkg/usrlocalsharelima/usrlocalsharelima.go index e22052ce54e..917fe57614d 100644 --- a/pkg/usrlocalsharelima/usrlocalsharelima.go +++ b/pkg/usrlocalsharelima/usrlocalsharelima.go @@ -16,7 +16,7 @@ import ( "github.com/sirupsen/logrus" "github.com/lima-vm/lima/v2/pkg/debugutil" - "github.com/lima-vm/lima/v2/pkg/limayaml" + "github.com/lima-vm/lima/v2/pkg/limatype" ) // executableViaArgs0 returns the absolute path to the executable used to start this process. @@ -69,8 +69,8 @@ func Dir() (string, error) { } } - ostype := limayaml.NewOS("linux") - arch := limayaml.NewArch(runtime.GOARCH) + ostype := limatype.NewOS("linux") + arch := limatype.NewArch(runtime.GOARCH) if arch == "" { return "", fmt.Errorf("failed to get arch for %q", runtime.GOARCH) } @@ -122,7 +122,7 @@ func Dir() (string, error) { } // GuestAgentBinary returns the absolute path of the guest agent binary, possibly with ".gz" suffix. -func GuestAgentBinary(ostype limayaml.OS, arch limayaml.Arch) (string, error) { +func GuestAgentBinary(ostype limatype.OS, arch limatype.Arch) (string, error) { if ostype == "" { return "", errors.New("os must be set") }