Skip to content

Commit c59ffed

Browse files
committed
Docker Container external driver
A new Linux driver similar to the WSL2 driver for Windows, creating virtual machines from container images (as rootfs). Signed-off-by: Anders F Björklund <[email protected]>
1 parent 7203808 commit c59ffed

File tree

12 files changed

+722
-3
lines changed

12 files changed

+722
-3
lines changed

cmd/lima-driver-dc/main_linux.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package main
5+
6+
import (
7+
"context"
8+
9+
"github.com/lima-vm/lima/v2/pkg/driver/dc"
10+
"github.com/lima-vm/lima/v2/pkg/driver/external/server"
11+
)
12+
13+
// To be used as an external driver for Lima.
14+
func main() {
15+
server.Serve(context.Background(), dc.New())
16+
}

pkg/cidata/cidata.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ func GenerateISO9660(ctx context.Context, drv driver.Driver, instDir, name strin
466466
})
467467
}
468468

469-
if args.VMType == limatype.WSL2 || args.VMType == limatype.AC {
469+
if args.VMType == limatype.WSL2 || args.VMType == limatype.AC || args.VMType == limatype.DC {
470470
layout = append(layout, iso9660util.Entry{
471471
Path: "ssh_authorized_keys",
472472
Reader: strings.NewReader(strings.Join(args.SSHPubKeys, "\n")),

pkg/driver/dc/boot/02-dc-setup.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/bin/sh
2+
3+
# SPDX-FileCopyrightText: Copyright The Lima Authors
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
# This script replaces the cloud-init functionality of creating a user and setting its SSH keys
7+
# when using a DC VM.
8+
[ "$LIMA_CIDATA_VMTYPE" = "dc" ] || exit 0
9+
10+
# create user
11+
# shellcheck disable=SC2153
12+
useradd -u "${LIMA_CIDATA_UID}" "${LIMA_CIDATA_USER}" -c "${LIMA_CIDATA_COMMENT}" -d "${LIMA_CIDATA_HOME}" -m -s "${LIMA_CIDATA_SHELL}"
13+
LIMA_CIDATA_GID=$(id -g "${LIMA_CIDATA_USER}")
14+
mkdir "${LIMA_CIDATA_HOME}"/.ssh/
15+
chown "${LIMA_CIDATA_UID}:${LIMA_CIDATA_GID}" "${LIMA_CIDATA_HOME}"/.ssh/
16+
chmod 700 "${LIMA_CIDATA_HOME}"/.ssh/
17+
cp "${LIMA_CIDATA_MNT}"/ssh_authorized_keys "${LIMA_CIDATA_HOME}"/.ssh/authorized_keys
18+
chown "${LIMA_CIDATA_UID}:${LIMA_CIDATA_GID}" "${LIMA_CIDATA_HOME}"/.ssh/authorized_keys
19+
chmod 600 "${LIMA_CIDATA_HOME}"/.ssh/authorized_keys
20+
21+
# add $LIMA_CIDATA_USER to sudoers
22+
echo "${LIMA_CIDATA_USER} ALL=(ALL) NOPASSWD:ALL" | tee -a /etc/sudoers.d/99_lima_sudoers
23+
24+
# symlink CIDATA to the hardcoded path for requirement checks (TODO: make this not hardcoded)
25+
[ "$LIMA_CIDATA_MNT" = "/mnt/lima-cidata" ] || ln -sfFn "${LIMA_CIDATA_MNT}" /mnt/lima-cidata

pkg/driver/dc/dc_driver_linux.go

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package dc
5+
6+
import (
7+
"context"
8+
"embed"
9+
"errors"
10+
"fmt"
11+
"net"
12+
"regexp"
13+
14+
"github.com/sirupsen/logrus"
15+
16+
"github.com/lima-vm/lima/v2/pkg/driver"
17+
"github.com/lima-vm/lima/v2/pkg/limatype"
18+
"github.com/lima-vm/lima/v2/pkg/limayaml"
19+
"github.com/lima-vm/lima/v2/pkg/ptr"
20+
"github.com/lima-vm/lima/v2/pkg/reflectutil"
21+
)
22+
23+
var knownYamlProperties = []string{
24+
"Arch",
25+
"Containerd",
26+
"CopyToHost",
27+
"CPUType",
28+
"Disk",
29+
"DNS",
30+
"Env",
31+
"HostResolver",
32+
"Images",
33+
"Message",
34+
"Mounts",
35+
"MountType",
36+
"Param",
37+
"Plain",
38+
"PortForwards",
39+
"Probes",
40+
"PropagateProxyEnv",
41+
"Provision",
42+
"SSH",
43+
"VMType",
44+
}
45+
46+
const Enabled = true
47+
48+
type LimaDcDriver struct {
49+
Instance *limatype.Instance
50+
51+
SSHLocalPort int
52+
vSockPort int
53+
virtioPort string
54+
}
55+
56+
var _ driver.Driver = (*LimaDcDriver)(nil)
57+
58+
func New() *LimaDcDriver {
59+
return &LimaDcDriver{
60+
vSockPort: 0,
61+
virtioPort: "",
62+
}
63+
}
64+
65+
func (l *LimaDcDriver) Configure(inst *limatype.Instance) *driver.ConfiguredDriver {
66+
l.Instance = inst
67+
l.SSHLocalPort = inst.SSHLocalPort
68+
69+
return &driver.ConfiguredDriver{
70+
Driver: l,
71+
}
72+
}
73+
74+
func (l *LimaDcDriver) FillConfig(ctx context.Context, cfg *limatype.LimaYAML, _ string) error {
75+
if cfg.VMType == nil {
76+
cfg.VMType = ptr.Of(limatype.DC)
77+
}
78+
if cfg.MountType == nil {
79+
cfg.MountType = ptr.Of(limatype.WSLMount)
80+
}
81+
return validateConfig(ctx, cfg)
82+
}
83+
84+
func (l *LimaDcDriver) Validate(ctx context.Context) error {
85+
return validateConfig(ctx, l.Instance.Config)
86+
}
87+
88+
func validateConfig(_ context.Context, cfg *limatype.LimaYAML) error {
89+
if cfg == nil {
90+
return errors.New("configuration is nil")
91+
}
92+
if *cfg.MountType != limatype.REVSSHFS {
93+
return fmt.Errorf("field `mountType` must be %q for DC driver, got %q", limatype.REVSSHFS, *cfg.MountType)
94+
}
95+
// TODO: revise this list for DC
96+
if cfg.VMType != nil {
97+
if unknown := reflectutil.UnknownNonEmptyFields(cfg, knownYamlProperties...); len(unknown) > 0 {
98+
logrus.Warnf("Ignoring: vmType %s: %+v", *cfg.VMType, unknown)
99+
}
100+
}
101+
102+
if !limayaml.IsNativeArch(*cfg.Arch) {
103+
return fmt.Errorf("unsupported arch: %q", *cfg.Arch)
104+
}
105+
106+
if cfg.VMType != nil {
107+
if cfg.Images != nil && cfg.Arch != nil {
108+
// TODO: real filetype checks
109+
tarFileRegex := regexp.MustCompile(`.*tar\.*`)
110+
for i, image := range cfg.Images {
111+
if unknown := reflectutil.UnknownNonEmptyFields(image, "File"); len(unknown) > 0 {
112+
logrus.Warnf("Ignoring: vmType %s: images[%d]: %+v", *cfg.VMType, i, unknown)
113+
}
114+
match := tarFileRegex.MatchString(image.Location)
115+
if image.Arch == *cfg.Arch && !match {
116+
return fmt.Errorf("unsupported image type for vmType: %s, tarball root file system required: %q", *cfg.VMType, image.Location)
117+
}
118+
}
119+
}
120+
121+
if cfg.Mounts != nil {
122+
for i, mount := range cfg.Mounts {
123+
if unknown := reflectutil.UnknownNonEmptyFields(mount); len(unknown) > 0 {
124+
logrus.Warnf("Ignoring: vmType %s: mounts[%d]: %+v", *cfg.VMType, i, unknown)
125+
}
126+
}
127+
}
128+
129+
if cfg.Networks != nil {
130+
for i, network := range cfg.Networks {
131+
if unknown := reflectutil.UnknownNonEmptyFields(network); len(unknown) > 0 {
132+
logrus.Warnf("Ignoring: vmType %s: networks[%d]: %+v", *cfg.VMType, i, unknown)
133+
}
134+
}
135+
}
136+
137+
if cfg.Audio.Device != nil {
138+
audioDevice := *cfg.Audio.Device
139+
if audioDevice != "" {
140+
logrus.Warnf("Ignoring: vmType %s: `audio.device`: %+v", *cfg.VMType, audioDevice)
141+
}
142+
}
143+
}
144+
145+
return nil
146+
}
147+
148+
//go:embed boot/*.sh
149+
var bootFS embed.FS
150+
151+
func (l *LimaDcDriver) BootScripts() (map[string][]byte, error) {
152+
scripts := make(map[string][]byte)
153+
154+
entries, err := bootFS.ReadDir("boot")
155+
if err != nil {
156+
return scripts, err
157+
}
158+
159+
for _, entry := range entries {
160+
if entry.IsDir() {
161+
continue
162+
}
163+
164+
content, err := bootFS.ReadFile("boot/" + entry.Name())
165+
if err != nil {
166+
return nil, err
167+
}
168+
169+
scripts[entry.Name()] = content
170+
}
171+
172+
return scripts, nil
173+
}
174+
175+
func (l *LimaDcDriver) InspectStatus(ctx context.Context, inst *limatype.Instance) string {
176+
status, err := getDcStatus(ctx, inst.Name)
177+
if err != nil {
178+
inst.Status = limatype.StatusBroken
179+
inst.Errors = append(inst.Errors, err)
180+
} else {
181+
inst.Status = status
182+
}
183+
184+
inst.SSHLocalPort = 22
185+
186+
if inst.Status == limatype.StatusRunning {
187+
sshAddr, err := getSSHAddress(ctx, inst.Name)
188+
if err == nil {
189+
inst.SSHAddress = sshAddr
190+
} else {
191+
inst.Errors = append(inst.Errors, err)
192+
}
193+
}
194+
195+
return inst.Status
196+
}
197+
198+
func (l *LimaDcDriver) Delete(ctx context.Context) error {
199+
distroName := "lima-" + l.Instance.Name
200+
status, err := getDcStatus(ctx, l.Instance.Name)
201+
if err != nil {
202+
return err
203+
}
204+
switch status {
205+
case limatype.StatusRunning, limatype.StatusStopped, limatype.StatusBroken, limatype.StatusInstalling:
206+
return deleteVM(ctx, distroName)
207+
}
208+
209+
logrus.Info("VM not registered, skipping unregistration")
210+
return nil
211+
}
212+
213+
func (l *LimaDcDriver) Start(ctx context.Context) (chan error, error) {
214+
logrus.Infof("Starting DC VM")
215+
status, err := getDcStatus(ctx, l.Instance.Name)
216+
if err != nil {
217+
return nil, err
218+
}
219+
220+
distroName := "lima-" + l.Instance.Name
221+
222+
if status == limatype.StatusUninitialized {
223+
if err := EnsureFs(ctx, l.Instance); err != nil {
224+
return nil, err
225+
}
226+
if err := initVM(ctx, l.Instance.Dir, distroName); err != nil {
227+
return nil, err
228+
}
229+
cpus := l.Instance.CPUs
230+
memory := int(l.Instance.Memory >> 20) // MiB
231+
if err := createVM(ctx, distroName, cpus, memory); err != nil {
232+
return nil, err
233+
}
234+
}
235+
236+
errCh := make(chan error)
237+
238+
if err := startVM(ctx, distroName); err != nil {
239+
return nil, err
240+
}
241+
242+
if err := provisionVM(
243+
ctx,
244+
l.Instance.Dir,
245+
l.Instance.Name,
246+
distroName,
247+
errCh,
248+
); err != nil {
249+
return nil, err
250+
}
251+
252+
return errCh, err
253+
}
254+
255+
func (l *LimaDcDriver) canRunGUI() bool {
256+
return false
257+
}
258+
259+
func (l *LimaDcDriver) RunGUI() error {
260+
return fmt.Errorf("RunGUI is not supported for the given driver '%s' and display '%s'", "dc", *l.Instance.Config.Video.Display)
261+
}
262+
263+
func (l *LimaDcDriver) Stop(ctx context.Context) error {
264+
logrus.Info("Shutting down DC VM")
265+
distroName := "lima-" + l.Instance.Name
266+
return stopVM(ctx, distroName)
267+
}
268+
269+
// GuestAgentConn returns the guest agent connection, or nil (if forwarded by ssh).
270+
func (l *LimaDcDriver) GuestAgentConn(_ context.Context) (net.Conn, string, error) {
271+
return nil, "", nil
272+
}
273+
274+
func (l *LimaDcDriver) Info() driver.Info {
275+
var info driver.Info
276+
info.Name = "dc"
277+
if l.Instance != nil {
278+
info.InstanceDir = l.Instance.Dir
279+
}
280+
info.VirtioPort = l.virtioPort
281+
info.VsockPort = l.vSockPort
282+
283+
info.Features = driver.DriverFeatures{
284+
DynamicSSHAddress: true,
285+
SkipSocketForwarding: true,
286+
CanRunGUI: l.canRunGUI(),
287+
}
288+
return info
289+
}
290+
291+
func (l *LimaDcDriver) SSHAddress(_ context.Context) (string, error) {
292+
return "127.0.0.1", nil
293+
}
294+
295+
func (l *LimaDcDriver) Create(_ context.Context) error {
296+
return nil
297+
}
298+
299+
func (l *LimaDcDriver) CreateDisk(_ context.Context) error {
300+
return nil
301+
}
302+
303+
func (l *LimaDcDriver) ChangeDisplayPassword(_ context.Context, _ string) error {
304+
return nil
305+
}
306+
307+
func (l *LimaDcDriver) DisplayConnection(_ context.Context) (string, error) {
308+
return "", nil
309+
}
310+
311+
func (l *LimaDcDriver) CreateSnapshot(_ context.Context, _ string) error {
312+
return errUnimplemented
313+
}
314+
315+
func (l *LimaDcDriver) ApplySnapshot(_ context.Context, _ string) error {
316+
return errUnimplemented
317+
}
318+
319+
func (l *LimaDcDriver) DeleteSnapshot(_ context.Context, _ string) error {
320+
return errUnimplemented
321+
}
322+
323+
func (l *LimaDcDriver) ListSnapshots(_ context.Context) (string, error) {
324+
return "", errUnimplemented
325+
}
326+
327+
func (l *LimaDcDriver) ForwardGuestAgent() bool {
328+
// If driver is not providing, use host agent
329+
return l.vSockPort == 0 && l.virtioPort == ""
330+
}

pkg/driver/dc/errors_linux.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 dc
5+
6+
import "errors"
7+
8+
var errUnimplemented = errors.New("unimplemented")

0 commit comments

Comments
 (0)