Skip to content

Commit 7dda143

Browse files
pendo324AkihiroSuda
andcommitted
ci: add WSL2 CI
Signed-off-by: Akihiro Suda <[email protected]> Signed-off-by: Justin Alvarez <[email protected]> Co-authored-by: Akihiro Suda <[email protected]>
1 parent ca85513 commit 7dda143

File tree

5 files changed

+137
-28
lines changed

5 files changed

+137
-28
lines changed

.github/workflows/test.yml

+40-1
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,35 @@ jobs:
7373

7474
windows:
7575
name: "Windows tests"
76-
runs-on: windows-2022
76+
runs-on: windows-2022-8-cores
7777
timeout-minutes: 30
7878
steps:
79+
- name: Enable WSL2
80+
run: |
81+
wsl --set-default-version 2
82+
# Manually install the latest kernel from MSI
83+
Invoke-WebRequest -Uri "https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi" -OutFile "wsl_update_x64.msi"
84+
$pwd = (pwd).Path
85+
Start-Process msiexec.exe -Wait -ArgumentList "/I $pwd\wsl_update_x64.msi /quiet"
86+
wsl --update
87+
wsl --status
88+
wsl --list --online
89+
- name: Install WSL2 distro
90+
timeout-minutes: 3
91+
run: |
92+
# FIXME: At least one distro has to be installed here,
93+
# otherwise `wsl --list --verbose` (called from Lima) fails:
94+
# https://github.com/lima-vm/lima/pull/1826#issuecomment-1729993334
95+
# The distro image itself is not consumed by Lima.
96+
# ------------------------------------------------------------------
97+
# Ubuntu-22.04: gets stuck in some infinite loop during adduser
98+
# OracleLinux_9_1: almostly silently fails, and just prints "Usage: adduser [options] LOGIN"
99+
wsl --install -d openSUSE-Leap-15.5
100+
wsl --list --verbose
101+
- name: Set gitconfig
102+
run: |
103+
git config --global core.autocrlf false
104+
git config --global core.eol lf
79105
- uses: actions/checkout@v4
80106
with:
81107
fetch-depth: 1
@@ -86,6 +112,19 @@ jobs:
86112
run: go test -v ./...
87113
- name: Make
88114
run: make
115+
- name: Smoke test
116+
# Make sure the path is set properly and then run limactl
117+
run: |
118+
$env:Path = 'C:\Program Files\Git\usr\bin;' + $env:Path
119+
Set-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment' -Name PATH -Value $env:Path
120+
.\_output\bin\limactl.exe start template://experimental/wsl2
121+
# TODO: run the full integration tests
122+
- name: Debug
123+
if: always()
124+
run: type C:\Users\runneradmin\.lima\wsl2\ha.stdout.log
125+
- name: Debug
126+
if: always()
127+
run: type C:\Users\runneradmin\.lima\wsl2\ha.stderr.log
89128

90129
integration:
91130
name: Integration tests

examples/experimental/wsl2.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ vmType: wsl2
44

55
images:
66
# Source: https://github.com/runfinch/finch-core/blob/main/Dockerfile
7-
- location: "https://deps.runfinch.com/common/x86-64/finch-rootfs-production-amd64-1690920103.tar.zst"
7+
- location: "https://deps.runfinch.com/common/x86-64/finch-rootfs-production-amd64-1694791577.tar.gz"
88
arch: "x86_64"
9-
digest: "sha256:53f2e329b8da0f6a25e025d1f6cc262ae228402ba615ad095739b2f0ec6babc9"
9+
digest: "sha256:2d4d2e7386450899c6d0587fd0db21afadb31d974fa744aa9365c883935c5341"
1010

1111
mountType: wsl2
1212

pkg/store/instance_windows.go

+38-5
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,33 @@ func inspectStatus(instDir string, inst *Instance, y *limayaml.LimaYAML) {
3535
}
3636
}
3737

38-
// GetWslStatus runs `wsl --list --verbose` and parses its output
38+
// GetWslStatus runs `wsl --list --verbose` and parses its output.
39+
// There are several possible outputs, all listed with their whitespace preserved output below.
3940
//
40-
// Expected output (whitespace preserved):
41+
// (1) Expected output if at least one distro is installed:
4142
// PS > wsl --list --verbose
4243
//
4344
// NAME STATE VERSION
4445
//
4546
// * Ubuntu Stopped 2
47+
//
48+
// (2) Expected output when no distros are installed, but WSL is configured properly:
49+
// PS > wsl --list --verbose
50+
// Windows Subsystem for Linux has no installed distributions.
51+
//
52+
// Use 'wsl.exe --list --online' to list available distributions
53+
// and 'wsl.exe --install <Distro>' to install.
54+
//
55+
// Distributions can also be installed by visiting the Microsoft Store:
56+
// https://aka.ms/wslstore
57+
// Error code: Wsl/WSL_E_DEFAULT_DISTRO_NOT_FOUND
58+
//
59+
// (3) Expected output when no distros are installed, and WSL2 has no kernel installed:
60+
//
61+
// PS > wsl --list --verbose
62+
// Windows Subsystem for Linux has no installed distributions.
63+
// Distributions can be installed by visiting the Microsoft Store:
64+
// https://aka.ms/wslstore
4665
func GetWslStatus(instName string) (string, error) {
4766
distroName := "lima-" + instName
4867
out, err := executil.RunUTF16leCommand([]string{
@@ -51,11 +70,25 @@ func GetWslStatus(instName string) (string, error) {
5170
"--verbose",
5271
})
5372
if err != nil {
54-
return "", fmt.Errorf("failed to run `wsl --list --verbose`, err: %w", err)
73+
return "", fmt.Errorf("failed to run `wsl --list --verbose`, err: %w (out=%q)", err, string(out))
5574
}
5675

5776
if len(out) == 0 {
58-
return StatusBroken, fmt.Errorf("failed to read instance state for instance %s, try running `wsl --list --verbose` to debug, err: %w", instName, err)
77+
return StatusBroken, fmt.Errorf("failed to read instance state for instance %q, try running `wsl --list --verbose` to debug, err: %w", instName, err)
78+
}
79+
80+
// Check for edge cases first
81+
outString := string(out)
82+
if strings.Contains(outString, "Windows Subsystem for Linux has no installed distributions.") {
83+
if strings.Contains(outString, "Wsl/WSL_E_DEFAULT_DISTRO_NOT_FOUND") {
84+
return StatusBroken, fmt.Errorf(
85+
"failed to read instance state for instance %q because no distro is installed,"+
86+
"try running `wsl --install -d Ubuntu` and then re-running Lima", instName)
87+
}
88+
return StatusBroken, fmt.Errorf(
89+
"failed to read instance state for instance %q because there is no WSL kernel installed,"+
90+
"this usually happens when WSL was installed for another user, but never for your user."+
91+
"Try running `wsl --install -d Ubuntu` and `wsl --update`, and then re-running Lima", instName)
5992
}
6093

6194
var instState string
@@ -94,7 +127,7 @@ func getWslSSHAddress(instName string) (string, error) {
94127
cmd := exec.Command("wsl.exe", "-d", distroName, "bash", "-c", `hostname -I | cut -d ' ' -f1`)
95128
out, err := cmd.CombinedOutput()
96129
if err != nil {
97-
return "", fmt.Errorf("failed to get hostname for instance %s, err: %w", instName, err)
130+
return "", fmt.Errorf("failed to get hostname for instance %q, err: %w (out=%q)", instName, err, string(out))
98131
}
99132

100133
return strings.TrimSpace(string(out)), nil

pkg/wsl2/lima-init.TEMPLATE

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
set -eu; \
2-
export LOG_FILE=/var/log/lima-init.log; \
3-
exec > >(tee \$LOG_FILE) 2>&1; \
4-
export LIMA_CIDATA_MNT="$(/usr/bin/wslpath '{{.CIDataPath}}')"; \
5-
exec "\$LIMA_CIDATA_MNT/boot.sh";
1+
#!/bin/bash
2+
set -eu
3+
export LOG_FILE=/var/log/lima-init.log
4+
exec > >(tee $LOG_FILE) 2>&1
5+
export LIMA_CIDATA_MNT="$(/usr/bin/wslpath '{{.CIDataPath}}')"
6+
exec "$LIMA_CIDATA_MNT/boot.sh"

pkg/wsl2/vm_windows.go

+51-15
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import (
77
"context"
88
_ "embed"
99
"fmt"
10+
"os"
1011
"os/exec"
1112
"path/filepath"
13+
"strconv"
1214
"strings"
1315

1416
"github.com/lima-vm/lima/pkg/executil"
@@ -20,13 +22,14 @@ import (
2022

2123
// startVM calls WSL to start a VM.
2224
func startVM(ctx context.Context, distroName string) error {
23-
_, err := executil.RunUTF16leCommand([]string{
25+
out, err := executil.RunUTF16leCommand([]string{
2426
"wsl.exe",
2527
"--distribution",
2628
distroName,
2729
}, executil.WithContext(&ctx))
2830
if err != nil {
29-
return err
31+
return fmt.Errorf("failed to run `wsl.exe --distribution %s`: %w (out=%q)",
32+
distroName, err, string(out))
3033
}
3134
return nil
3235
}
@@ -35,28 +38,30 @@ func startVM(ctx context.Context, distroName string) error {
3538
func initVM(ctx context.Context, instanceDir, distroName string) error {
3639
baseDisk := filepath.Join(instanceDir, filenames.BaseDisk)
3740
logrus.Infof("Importing distro from %q to %q", baseDisk, instanceDir)
38-
_, err := executil.RunUTF16leCommand([]string{
41+
out, err := executil.RunUTF16leCommand([]string{
3942
"wsl.exe",
4043
"--import",
4144
distroName,
4245
instanceDir,
4346
baseDisk,
4447
}, executil.WithContext(&ctx))
4548
if err != nil {
46-
return err
49+
return fmt.Errorf("failed to run `wsl.exe --import %s %s %s`: %w (out=%q)",
50+
distroName, instanceDir, baseDisk, err, string(out))
4751
}
4852
return nil
4953
}
5054

5155
// stopVM calls WSL to stop a running VM.
5256
func stopVM(ctx context.Context, distroName string) error {
53-
_, err := executil.RunUTF16leCommand([]string{
57+
out, err := executil.RunUTF16leCommand([]string{
5458
"wsl.exe",
5559
"--terminate",
5660
distroName,
5761
}, executil.WithContext(&ctx))
5862
if err != nil {
59-
return err
63+
return fmt.Errorf("failed to run `wsl.exe --terminate %s`: %w (out=%q)",
64+
distroName, err, string(out))
6065
}
6166
return nil
6267
}
@@ -70,12 +75,39 @@ func provisionVM(ctx context.Context, instanceDir, instanceName, distroName stri
7075
m := map[string]string{
7176
"CIDataPath": ciDataPath,
7277
}
73-
out, err := textutil.ExecuteTemplate(limaBoot, m)
78+
limaBootB, err := textutil.ExecuteTemplate(limaBoot, m)
7479
if err != nil {
7580
return fmt.Errorf("failed to construct wsl boot.sh script: %w", err)
7681
}
77-
outString := strings.Replace(string(out), `\r\n`, `\n`, -1)
78-
82+
limaBootFile, err := os.CreateTemp("", "lima-wsl2-boot-*.sh")
83+
if err != nil {
84+
return err
85+
}
86+
if _, err = limaBootFile.Write(limaBootB); err != nil {
87+
return err
88+
}
89+
limaBootFileWinPath := limaBootFile.Name()
90+
if err = limaBootFile.Close(); err != nil {
91+
return err
92+
}
93+
// path should be quoted and use \\ as separator
94+
bootFileWSLPath := strconv.Quote(limaBootFileWinPath)
95+
limaBootFilePathOnLinuxB, err := exec.Command(
96+
"wsl.exe",
97+
"-d",
98+
distroName,
99+
"bash",
100+
"-c",
101+
fmt.Sprintf("wslpath -u %s", bootFileWSLPath),
102+
bootFileWSLPath,
103+
).Output()
104+
if err != nil {
105+
os.RemoveAll(limaBootFileWinPath)
106+
// this can return an error with an exit code, which causes it not to be logged
107+
// because main.handleExitCoder() traps it, so wrap the error
108+
return fmt.Errorf("failed to run wslpath command: %w", err)
109+
}
110+
limaBootFileLinuxPath := strings.TrimSpace(string(limaBootFilePathOnLinuxB))
79111
go func() {
80112
cmd := exec.CommandContext(
81113
ctx,
@@ -84,12 +116,15 @@ func provisionVM(ctx context.Context, instanceDir, instanceName, distroName stri
84116
distroName,
85117
"bash",
86118
"-c",
87-
outString,
119+
limaBootFileLinuxPath,
88120
)
89-
if _, err := cmd.CombinedOutput(); err != nil {
121+
out, err := cmd.CombinedOutput()
122+
os.RemoveAll(limaBootFileWinPath)
123+
logrus.Debugf("%v: %q", cmd.Args, string(out))
124+
if err != nil {
90125
*errCh <- fmt.Errorf(
91-
"error running wslCommand that executes boot.sh: %w, "+
92-
"check /var/log/lima-init.log for more details", err)
126+
"error running wslCommand that executes boot.sh (%v): %w, "+
127+
"check /var/log/lima-init.log for more details (out=%q)", cmd.Args, err, string(out))
93128
}
94129

95130
for {
@@ -130,13 +165,14 @@ func keepAlive(ctx context.Context, distroName string, errCh *chan error) {
130165
// unregisterVM calls WSL to unregister a VM.
131166
func unregisterVM(ctx context.Context, distroName string) error {
132167
logrus.Info("Unregistering WSL2 VM")
133-
_, err := executil.RunUTF16leCommand([]string{
168+
out, err := executil.RunUTF16leCommand([]string{
134169
"wsl.exe",
135170
"--unregister",
136171
distroName,
137172
}, executil.WithContext(&ctx))
138173
if err != nil {
139-
return err
174+
return fmt.Errorf("failed to run `wsl.exe --unregister %s`: %w (out=%q)",
175+
distroName, err, string(out))
140176
}
141177
return nil
142178
}

0 commit comments

Comments
 (0)