diff --git a/pkg/driver/wsl2/vm_windows.go b/pkg/driver/wsl2/vm_windows.go index 6360127083f..6d493627c11 100644 --- a/pkg/driver/wsl2/vm_windows.go +++ b/pkg/driver/wsl2/vm_windows.go @@ -7,6 +7,7 @@ import ( "context" _ "embed" "fmt" + "net" "os" "os/exec" "path/filepath" @@ -134,7 +135,7 @@ func provisionVM(ctx context.Context, instanceDir, instanceName, distroName stri for { <-ctx.Done() logrus.Info("Context closed, stopping vm") - if status, err := getWslStatus(instanceName); err == nil && + if status, err := getWslStatus(ctx, instanceName); err == nil && status == limatype.StatusRunning { _ = stopVM(ctx, distroName) } @@ -179,13 +180,40 @@ func unregisterVM(ctx context.Context, distroName string) error { return nil } -func getWslStatus(instName string) (string, error) { +// 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(ctx context.Context, instName string) (string, error) { distroName := "lima-" + instName out, err := executil.RunUTF16leCommand([]string{ "wsl.exe", "--list", "--verbose", - }) + }, executil.WithContext(ctx)) if err != nil { return "", fmt.Errorf("failed to run `wsl --list --verbose`, err: %w (out=%q)", err, out) } @@ -229,3 +257,37 @@ func getWslStatus(instName string) (string, error) { return instState, nil } + +// GetSSHAddress runs a hostname command to get the IP from inside of a wsl2 VM. +// +// Expected output (whitespace preserved, [] for optional): +// PS > wsl -d bash -c hostname -I | cut -d' ' -f1 +// 168.1.1.1 [10.0.0.1] +// But busybox hostname does not implement --all-ip-addresses: +// hostname: unrecognized option: I +func getSSHAddress(ctx context.Context, instName string) (string, error) { + distroName := "lima-" + instName + // Ubuntu + cmd := exec.CommandContext(ctx, "wsl.exe", "-d", distroName, "bash", "-c", `hostname -I | cut -d ' ' -f1`) + out, err := cmd.CombinedOutput() + if err == nil { + return strings.TrimSpace(string(out)), nil + } + // Alpine + cmd = exec.CommandContext(ctx, "wsl.exe", "-d", distroName, "sh", "-c", `ip route get 1 | awk '{gsub("^.*src ",""); print $1; exit}'`) + out, err = cmd.CombinedOutput() + if err == nil { + return strings.TrimSpace(string(out)), nil + } + // fallback + cmd = exec.CommandContext(ctx, "wsl.exe", "-d", distroName, "hostname", "-i") + out, err = cmd.CombinedOutput() + if err == nil { + ip := net.ParseIP(strings.TrimSpace(string(out))) + // some distributions use "127.0.1.1" as the host IP, but we want something that we can route to here + if ip != nil && !ip.IsLoopback() { + return strings.TrimSpace(string(out)), nil + } + } + return "", fmt.Errorf("failed to get hostname for instance %q, err: %w (out=%q)", instName, err, string(out)) +} diff --git a/pkg/driver/wsl2/wsl_driver_windows.go b/pkg/driver/wsl2/wsl_driver_windows.go index ae8252b1639..8c48c0ad07a 100644 --- a/pkg/driver/wsl2/wsl_driver_windows.go +++ b/pkg/driver/wsl2/wsl_driver_windows.go @@ -182,7 +182,7 @@ func (l *LimaWslDriver) BootScripts() (map[string][]byte, error) { } func (l *LimaWslDriver) InspectStatus(ctx context.Context, inst *limatype.Instance) string { - status, err := getWslStatus(inst.Name) + status, err := getWslStatus(ctx, inst.Name) if err != nil { inst.Status = limatype.StatusBroken inst.Errors = append(inst.Errors, err) @@ -193,7 +193,7 @@ func (l *LimaWslDriver) InspectStatus(ctx context.Context, inst *limatype.Instan inst.SSHLocalPort = 22 if inst.Status == limatype.StatusRunning { - sshAddr, err := l.SSHAddress(ctx) + sshAddr, err := getSSHAddress(ctx, inst.Name) if err == nil { inst.SSHAddress = sshAddr } else { @@ -206,7 +206,7 @@ func (l *LimaWslDriver) InspectStatus(ctx context.Context, inst *limatype.Instan func (l *LimaWslDriver) Delete(ctx context.Context) error { distroName := "lima-" + l.Instance.Name - status, err := getWslStatus(l.Instance.Name) + status, err := getWslStatus(ctx, l.Instance.Name) if err != nil { return err } @@ -221,7 +221,7 @@ func (l *LimaWslDriver) Delete(ctx context.Context) error { func (l *LimaWslDriver) Start(ctx context.Context) (chan error, error) { logrus.Infof("Starting WSL VM") - status, err := getWslStatus(l.Instance.Name) + status, err := getWslStatus(ctx, l.Instance.Name) if err != nil { return nil, err }