|
7 | 7 | "context" |
8 | 8 | _ "embed" |
9 | 9 | "fmt" |
| 10 | + "net" |
10 | 11 | "os" |
11 | 12 | "os/exec" |
12 | 13 | "path/filepath" |
@@ -134,7 +135,7 @@ func provisionVM(ctx context.Context, instanceDir, instanceName, distroName stri |
134 | 135 | for { |
135 | 136 | <-ctx.Done() |
136 | 137 | logrus.Info("Context closed, stopping vm") |
137 | | - if status, err := getWslStatus(instanceName); err == nil && |
| 138 | + if status, err := getWslStatus(ctx, instanceName); err == nil && |
138 | 139 | status == limatype.StatusRunning { |
139 | 140 | _ = stopVM(ctx, distroName) |
140 | 141 | } |
@@ -179,13 +180,40 @@ func unregisterVM(ctx context.Context, distroName string) error { |
179 | 180 | return nil |
180 | 181 | } |
181 | 182 |
|
182 | | -func getWslStatus(instName string) (string, error) { |
| 183 | +// GetWslStatus runs `wsl --list --verbose` and parses its output. |
| 184 | +// There are several possible outputs, all listed with their whitespace preserved output below. |
| 185 | +// |
| 186 | +// (1) Expected output if at least one distro is installed: |
| 187 | +// PS > wsl --list --verbose |
| 188 | +// |
| 189 | +// NAME STATE VERSION |
| 190 | +// |
| 191 | +// * Ubuntu Stopped 2 |
| 192 | +// |
| 193 | +// (2) Expected output when no distros are installed, but WSL is configured properly: |
| 194 | +// PS > wsl --list --verbose |
| 195 | +// Windows Subsystem for Linux has no installed distributions. |
| 196 | +// |
| 197 | +// Use 'wsl.exe --list --online' to list available distributions |
| 198 | +// and 'wsl.exe --install <Distro>' to install. |
| 199 | +// |
| 200 | +// Distributions can also be installed by visiting the Microsoft Store: |
| 201 | +// https://aka.ms/wslstore |
| 202 | +// Error code: Wsl/WSL_E_DEFAULT_DISTRO_NOT_FOUND |
| 203 | +// |
| 204 | +// (3) Expected output when no distros are installed, and WSL2 has no kernel installed: |
| 205 | +// |
| 206 | +// PS > wsl --list --verbose |
| 207 | +// Windows Subsystem for Linux has no installed distributions. |
| 208 | +// Distributions can be installed by visiting the Microsoft Store: |
| 209 | +// https://aka.ms/wslstore |
| 210 | +func getWslStatus(ctx context.Context, instName string) (string, error) { |
183 | 211 | distroName := "lima-" + instName |
184 | 212 | out, err := executil.RunUTF16leCommand([]string{ |
185 | 213 | "wsl.exe", |
186 | 214 | "--list", |
187 | 215 | "--verbose", |
188 | | - }) |
| 216 | + }, executil.WithContext(ctx)) |
189 | 217 | if err != nil { |
190 | 218 | return "", fmt.Errorf("failed to run `wsl --list --verbose`, err: %w (out=%q)", err, out) |
191 | 219 | } |
@@ -229,3 +257,37 @@ func getWslStatus(instName string) (string, error) { |
229 | 257 |
|
230 | 258 | return instState, nil |
231 | 259 | } |
| 260 | + |
| 261 | +// GetSSHAddress runs a hostname command to get the IP from inside of a wsl2 VM. |
| 262 | +// |
| 263 | +// Expected output (whitespace preserved, [] for optional): |
| 264 | +// PS > wsl -d <distroName> bash -c hostname -I | cut -d' ' -f1 |
| 265 | +// 168.1.1.1 [10.0.0.1] |
| 266 | +// But busybox hostname does not implement --all-ip-addresses: |
| 267 | +// hostname: unrecognized option: I |
| 268 | +func getSSHAddress(ctx context.Context, instName string) (string, error) { |
| 269 | + distroName := "lima-" + instName |
| 270 | + // Ubuntu |
| 271 | + cmd := exec.CommandContext(ctx, "wsl.exe", "-d", distroName, "bash", "-c", `hostname -I | cut -d ' ' -f1`) |
| 272 | + out, err := cmd.CombinedOutput() |
| 273 | + if err == nil { |
| 274 | + return strings.TrimSpace(string(out)), nil |
| 275 | + } |
| 276 | + // Alpine |
| 277 | + cmd = exec.CommandContext(ctx, "wsl.exe", "-d", distroName, "sh", "-c", `ip route get 1 | awk '{gsub("^.*src ",""); print $1; exit}'`) |
| 278 | + out, err = cmd.CombinedOutput() |
| 279 | + if err == nil { |
| 280 | + return strings.TrimSpace(string(out)), nil |
| 281 | + } |
| 282 | + // fallback |
| 283 | + cmd = exec.CommandContext(ctx, "wsl.exe", "-d", distroName, "hostname", "-i") |
| 284 | + out, err = cmd.CombinedOutput() |
| 285 | + if err == nil { |
| 286 | + ip := net.ParseIP(strings.TrimSpace(string(out))) |
| 287 | + // some distributions use "127.0.1.1" as the host IP, but we want something that we can route to here |
| 288 | + if ip != nil && !ip.IsLoopback() { |
| 289 | + return strings.TrimSpace(string(out)), nil |
| 290 | + } |
| 291 | + } |
| 292 | + return "", fmt.Errorf("failed to get hostname for instance %q, err: %w (out=%q)", instName, err, string(out)) |
| 293 | +} |
0 commit comments