Skip to content

Commit 1bbd09a

Browse files
committed
Add ext driver for external VM machines
Don't show log for already cached archive, don't generate cidata.iso for external vm. It is not using cloud-init anyway, and does not need another copy of lima-guestagent and nerdctl-full.tgz Signed-off-by: Anders F Björklund <anders.f.bjorklund@gmail.com>
1 parent acfd7b7 commit 1bbd09a

File tree

13 files changed

+270
-17
lines changed

13 files changed

+270
-17
lines changed

cmd/limactl/main_ext.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package main
5+
6+
// Import ext driver to register it in the registry on all platforms.
7+
import _ "github.com/lima-vm/lima/v2/pkg/driver/ext"

pkg/driver/ext/errors.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ext
5+
6+
import "errors"
7+
8+
var errUnimplemented = errors.New("unimplemented")

pkg/driver/ext/ext_driver.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ext
5+
6+
import (
7+
"context"
8+
"errors"
9+
"fmt"
10+
"net"
11+
"slices"
12+
13+
"github.com/lima-vm/lima/v2/pkg/driver"
14+
"github.com/lima-vm/lima/v2/pkg/limatype"
15+
"github.com/lima-vm/lima/v2/pkg/ptr"
16+
)
17+
18+
type LimaExtDriver struct {
19+
Instance *limatype.Instance
20+
}
21+
22+
var _ driver.Driver = (*LimaExtDriver)(nil)
23+
24+
func New() *LimaExtDriver {
25+
return &LimaExtDriver{}
26+
}
27+
28+
func (l *LimaExtDriver) Configure(inst *limatype.Instance) *driver.ConfiguredDriver {
29+
l.Instance = inst
30+
31+
return &driver.ConfiguredDriver{
32+
Driver: l,
33+
}
34+
}
35+
36+
func (l *LimaExtDriver) Validate(ctx context.Context) error {
37+
return validateConfig(ctx, l.Instance.Config)
38+
}
39+
40+
func validateConfig(_ context.Context, cfg *limatype.LimaYAML) error {
41+
if cfg == nil {
42+
return errors.New("configuration is nil")
43+
}
44+
if err := validateMountType(cfg); err != nil {
45+
return err
46+
}
47+
48+
return nil
49+
}
50+
51+
// Helper method for mount type validation.
52+
func validateMountType(cfg *limatype.LimaYAML) error {
53+
if cfg.MountTypesUnsupported != nil && cfg.MountType != nil && slices.Contains(cfg.MountTypesUnsupported, *cfg.MountType) {
54+
return fmt.Errorf("mount type %q is explicitly unsupported", *cfg.MountType)
55+
}
56+
57+
return nil
58+
}
59+
60+
func (l *LimaExtDriver) FillConfig(_ context.Context, cfg *limatype.LimaYAML, _ string) error {
61+
if cfg.VMType == nil {
62+
cfg.VMType = ptr.Of(limatype.EXT)
63+
}
64+
65+
if cfg.MountType == nil || *cfg.MountType == "" || *cfg.MountType == "default" {
66+
cfg.MountType = ptr.Of(limatype.REVSSHFS)
67+
}
68+
69+
mountTypesUnsupported := make(map[string]struct{})
70+
mountTypesUnsupported[limatype.NINEP] = struct{}{}
71+
mountTypesUnsupported[limatype.VIRTIOFS] = struct{}{}
72+
73+
if _, ok := mountTypesUnsupported[*cfg.MountType]; ok {
74+
return fmt.Errorf("mount type %q is explicitly unsupported", *cfg.MountType)
75+
}
76+
77+
return nil
78+
}
79+
80+
func (l *LimaExtDriver) Start(_ context.Context) (chan error, error) {
81+
errCh := make(chan error)
82+
83+
return errCh, nil
84+
}
85+
86+
func (l *LimaExtDriver) BootScripts() (map[string][]byte, error) {
87+
return nil, nil
88+
}
89+
90+
func (l *LimaExtDriver) RunGUI() error {
91+
return fmt.Errorf("RunGUI is not supported for the given driver '%s' and display '%s'", "ext", *l.Instance.Config.Video.Display)
92+
}
93+
94+
func (l *LimaExtDriver) Stop(_ context.Context) error {
95+
return nil
96+
}
97+
98+
func (l *LimaExtDriver) GuestAgentConn(_ context.Context) (net.Conn, string, error) {
99+
return nil, "", nil
100+
}
101+
102+
func (l *LimaExtDriver) Info() driver.Info {
103+
var info driver.Info
104+
info.Name = "ext"
105+
if l.Instance != nil && l.Instance.Dir != "" {
106+
info.InstanceDir = l.Instance.Dir
107+
}
108+
109+
info.Features = driver.DriverFeatures{
110+
DynamicSSHAddress: false,
111+
SkipSocketForwarding: false,
112+
CanRunGUI: false,
113+
}
114+
return info
115+
}
116+
117+
func (l *LimaExtDriver) SSHAddress(_ context.Context) (string, error) {
118+
return l.Instance.SSHAddress, nil
119+
}
120+
121+
func (l *LimaExtDriver) InspectStatus(_ context.Context, _ *limatype.Instance) string {
122+
return ""
123+
}
124+
125+
func (l *LimaExtDriver) Create(_ context.Context) error {
126+
return nil
127+
}
128+
129+
func (l *LimaExtDriver) Delete(_ context.Context) error {
130+
return nil
131+
}
132+
133+
func (l *LimaExtDriver) CreateDisk(_ context.Context) error {
134+
return nil
135+
}
136+
137+
func (l *LimaExtDriver) Register(_ context.Context) error {
138+
return nil
139+
}
140+
141+
func (l *LimaExtDriver) Unregister(_ context.Context) error {
142+
return nil
143+
}
144+
145+
func (l *LimaExtDriver) ChangeDisplayPassword(_ context.Context, _ string) error {
146+
return nil
147+
}
148+
149+
func (l *LimaExtDriver) DisplayConnection(_ context.Context) (string, error) {
150+
return "", nil
151+
}
152+
153+
func (l *LimaExtDriver) CreateSnapshot(_ context.Context, _ string) error {
154+
return errUnimplemented
155+
}
156+
157+
func (l *LimaExtDriver) ApplySnapshot(_ context.Context, _ string) error {
158+
return errUnimplemented
159+
}
160+
161+
func (l *LimaExtDriver) DeleteSnapshot(_ context.Context, _ string) error {
162+
return errUnimplemented
163+
}
164+
165+
func (l *LimaExtDriver) ListSnapshots(_ context.Context) (string, error) {
166+
return "", errUnimplemented
167+
}
168+
169+
func (l *LimaExtDriver) ForwardGuestAgent() bool {
170+
// If driver is not providing, use host agent
171+
return true
172+
}

pkg/driver/ext/register.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//go:build !external_ext
2+
3+
// SPDX-FileCopyrightText: Copyright The Lima Authors
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
package ext
7+
8+
import "github.com/lima-vm/lima/v2/pkg/registry"
9+
10+
func init() {
11+
registry.Register(New())
12+
}

pkg/hostagent/hostagent.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,10 @@ func New(ctx context.Context, instName string, stdout io.Writer, signalCh chan o
167167
if err := cidata.GenerateCloudConfig(ctx, inst.Dir, instName, inst.Config); err != nil {
168168
return nil, err
169169
}
170-
if err := cidata.GenerateISO9660(ctx, limaDriver, inst.Dir, instName, inst.Config, udpDNSLocalPort, tcpDNSLocalPort, o.guestAgentBinary, o.nerdctlArchive, vSockPort, virtioPort, noCloudInit); err != nil {
171-
return nil, err
170+
if *inst.Config.VMType != limatype.EXT {
171+
if err := cidata.GenerateISO9660(ctx, limaDriver, inst.Dir, instName, inst.Config, udpDNSLocalPort, tcpDNSLocalPort, o.guestAgentBinary, o.nerdctlArchive, vSockPort, virtioPort, noCloudInit); err != nil {
172+
return nil, err
173+
}
172174
}
173175

174176
sshExe, err := sshutil.NewSSHExe()

pkg/hostagent/requirements.go

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -132,22 +132,24 @@ If any private key under ~/.ssh is protected with a passphrase, you need to have
132132
if *a.instConfig.Plain {
133133
return req
134134
}
135-
req = append(req,
136-
requirement{
137-
description: "user session is ready for ssh",
138-
script: `#!/bin/bash
135+
if *a.instConfig.VMType != limatype.EXT {
136+
req = append(req,
137+
requirement{
138+
description: "user session is ready for ssh",
139+
script: `#!/bin/bash
139140
set -eux -o pipefail
140141
if ! timeout 30s bash -c "until sudo diff -q /run/lima-ssh-ready /mnt/lima-cidata/meta-data 2>/dev/null; do sleep 3; done"; then
141142
echo >&2 "not ready to start persistent ssh session"
142143
exit 1
143144
fi
144145
`,
145-
debugHint: `The boot sequence will terminate any existing user session after updating
146+
debugHint: `The boot sequence will terminate any existing user session after updating
146147
/etc/environment to make sure the session includes the new values.
147148
Terminating the session will break the persistent SSH tunnel, so
148149
it must not be created until the session reset is done.
149150
`,
150-
})
151+
})
152+
}
151153

152154
if *a.instConfig.MountType == limatype.REVSSHFS && len(a.instConfig.Mounts) > 0 {
153155
req = append(req, requirement{
@@ -229,20 +231,22 @@ Also see "/var/log/cloud-init-output.log" in the guest.
229231

230232
func (a *HostAgent) finalRequirements() []requirement {
231233
req := make([]requirement, 0)
232-
req = append(req,
233-
requirement{
234-
description: "boot scripts must have finished",
235-
script: `#!/bin/bash
234+
if *a.instConfig.VMType != limatype.EXT {
235+
req = append(req,
236+
requirement{
237+
description: "boot scripts must have finished",
238+
script: `#!/bin/bash
236239
set -eux -o pipefail
237240
if ! timeout 30s bash -c "until sudo diff -q /run/lima-boot-done /mnt/lima-cidata/meta-data 2>/dev/null; do sleep 3; done"; then
238241
echo >&2 "boot scripts have not finished"
239242
exit 1
240243
fi
241244
`,
242-
debugHint: `All boot scripts, provisioning scripts, and readiness probes must
245+
debugHint: `All boot scripts, provisioning scripts, and readiness probes must
243246
finish before the instance is considered "ready".
244247
Check "/var/log/cloud-init-output.log" in the guest to see where the process is blocked!
245248
`,
246-
})
249+
})
250+
}
247251
return req
248252
}

pkg/instance/start.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func Prepare(ctx context.Context, inst *limatype.Instance) (*Prepared, error) {
105105
ensuredBaseDisk = true
106106
break
107107
}
108-
if !ensuredBaseDisk {
108+
if !ensuredBaseDisk && *inst.Config.VMType != limatype.EXT {
109109
return nil, fileutils.Errors(errs)
110110
}
111111
}
@@ -119,6 +119,11 @@ func Prepare(ctx context.Context, inst *limatype.Instance) (*Prepared, error) {
119119
return nil, err
120120
}
121121

122+
if *inst.Config.VMType == limatype.EXT {
123+
// Created externally
124+
created = true
125+
}
126+
122127
nerdctlArchiveCache, err := cacheutil.EnsureNerdctlArchiveCache(ctx, inst.Config, created)
123128
if err != nil {
124129
return nil, err

pkg/limatype/lima_yaml.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,14 @@ const (
9393
QEMU VMType = "qemu"
9494
VZ VMType = "vz"
9595
WSL2 VMType = "wsl2"
96+
EXT VMType = "ext"
9697
)
9798

9899
var (
99100
OSTypes = []OS{LINUX}
100101
ArchTypes = []Arch{X8664, AARCH64, ARMV7L, PPC64LE, RISCV64, S390X}
101102
MountTypes = []MountType{REVSSHFS, NINEP, VIRTIOFS, WSLMount}
102-
VMTypes = []VMType{QEMU, VZ, WSL2}
103+
VMTypes = []VMType{QEMU, VZ, WSL2, EXT}
103104
)
104105

105106
type User struct {

pkg/limayaml/validate.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func Validate(y *limatype.LimaYAML, warn bool) error {
5858
errs = errors.Join(errs, fmt.Errorf("field `arch` must be one of %v; got %q", limatype.ArchTypes, *y.Arch))
5959
}
6060

61-
if len(y.Images) == 0 {
61+
if len(y.Images) == 0 && (y.VMType == nil || *y.VMType != limatype.EXT) {
6262
errs = errors.Join(errs, errors.New("field `images` must be set"))
6363
}
6464
for i, f := range y.Images {
@@ -143,6 +143,9 @@ func Validate(y *limatype.LimaYAML, warn bool) error {
143143
}
144144
}
145145

146+
if *y.SSH.Address == "127.0.0.1" && (y.VMType != nil && *y.VMType == limatype.EXT) {
147+
return errors.New("field `ssh.address` must be set, for ext")
148+
}
146149
if y.SSH.Address != nil {
147150
if err := validateHost("ssh.address", *y.SSH.Address); err != nil {
148151
return err
@@ -601,6 +604,9 @@ func warnExperimental(y *limatype.LimaYAML) {
601604
if *y.MountType == limatype.VIRTIOFS && runtime.GOOS == "linux" {
602605
logrus.Warn("`mountType: virtiofs` on Linux is experimental")
603606
}
607+
if *y.VMType == limatype.EXT {
608+
logrus.Warn("`vmType: ext` is experimental")
609+
}
604610
switch *y.Arch {
605611
case limatype.RISCV64, limatype.ARMV7L, limatype.S390X, limatype.PPC64LE:
606612
logrus.Warnf("`arch: %s ` is experimental", *y.Arch)

pkg/store/instance.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,14 @@ func inspectStatusWithPIDFiles(instDir string, inst *limatype.Instance, y *limat
170170
inst.Status = limatype.StatusBroken
171171
inst.Errors = append(inst.Errors, err)
172172
}
173+
if *y.VMType == limatype.EXT {
174+
if inst.HostAgentPID > 0 {
175+
inst.Status = limatype.StatusRunning
176+
} else if inst.HostAgentPID == 0 {
177+
inst.Status = limatype.StatusStopped
178+
}
179+
return
180+
}
173181

174182
if inst.Status == limatype.StatusUnknown {
175183
switch {

0 commit comments

Comments
 (0)