Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows/WSL2 Interoperability Things #164

Merged
merged 8 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
## {{ UNRELEASED_VERSION }} - [{{ UNRELEASED_DATE }}]({{ UNRELEASED_LINK }})

### New Feature
### New Features

* Updated `sql-export.sh` to use `mariadb-dump` command (if available). [#148](https://github.com/lando/core/pull/148)

* Added ability to autostart Docker Desktop for Windows from within WSL instances
* Improved method for locating and starting Docker Desktop on Windows
* Updated default Docker Compose version to `2.27.0`
* Updated default Docker Desktop version to `4.30.0`
* Updated default Docker Engine version to `26.1.1`
Expand All @@ -11,6 +14,7 @@

### Bug Fixes

* Fixed bug that caused Lando to be too impatient when starting Docker Desktop
* Fixed unclear error when cancelling certain prompts

### Internal
Expand Down
21 changes: 16 additions & 5 deletions hooks/lando-autostart-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ module.exports = async lando => {
const debug = require('../utils/debug-shim')(lando.log);
const tasks = [{
title: 'It seems Docker is not running, trying to start it up...',
retry: 25,
delay: 1000,
retry: {
tries: 25,
delay: 1000,
},
task: async (ctx, task) => {
// prompt for password if interactive and we dont have it
if (process.platform === 'linux' && lando.config.isInteractive) {
// Prompt for sudo password if interactive and not Docker Desktop WSL2 integration
if (
process.platform === 'linux'
&& lando.config.isInteractive
&& !require('../utils/is-wsl-interop')(lando.engine.daemon.docker)
) {
ctx.password = await task.prompt({
type: 'password',
name: 'password',
Expand All @@ -38,6 +44,11 @@ module.exports = async lando => {
}
},
}];
await lando.runTasks(tasks, {listrOptions: {exitOnError: true}});
await lando.runTasks(tasks, {
listrOptions: {exitOnError: true},
rendererOptions: {
pausedTimer: {condition: false},
},
});
}
};
44 changes: 33 additions & 11 deletions lib/daemon.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,23 @@ const macOSBase = '/Applications/Docker.app';
/*
* Get services wrapper
*/
const buildDockerCmd = (cmd, scriptsDir) => {
const buildDockerCmd = (cmd, scriptsDir, dockerBin) => {
const windowsStartScript = path.join(scriptsDir, 'docker-desktop-start.ps1');
switch (process.platform) {
case 'darwin':
return ['open', macOSBase];
case 'linux':
if (require('../utils/is-wsl-interop')(dockerBin)) {
// Docker Desktop WSL2 integration detected
return [
'powershell.exe', '-ExecutionPolicy', 'Bypass', '-File', `"${windowsStartScript}"`,
];
}
return [path.join(scriptsDir, `docker-engine-${cmd}.sh`)];
case 'win32':
const base = process.env.ProgramW6432 || process.env.ProgramFiles;
const dockerBin = base + '\\Docker\\Docker\\Docker Desktop.exe';
return ['cmd', '/C', `"${dockerBin}"`];
return [
'powershell.exe', '-ExecutionPolicy', 'Bypass', '-File', `"${windowsStartScript}"`,
];
}
};

Expand Down Expand Up @@ -86,12 +93,19 @@ module.exports = class LandoDaemon {
if (this.context !== 'node' && process.platform === 'linux') return Promise.resolve(true);

// special handling for linux
if (this.context === 'node' && process.platform === 'linux') {
if (
this.context === 'node'
&& process.platform === 'linux'
&& !require('../utils/is-wsl-interop')(this.docker)
) {
return this.isUp().then(async isUp => {
if (!isUp) {
const debug = require('../utils/debug-shim')(this.log);
try {
await require('../utils/run-elevated')(buildDockerCmd('start', this.scriptsDir), {debug, password});
await require('../utils/run-elevated')(
buildDockerCmd('start', this.scriptsDir, this.docker),
{debug, password},
);
return Promise.resolve(true);
} catch (error) {
this.log.debug('%j', error);
Expand All @@ -105,7 +119,7 @@ module.exports = class LandoDaemon {
return this.isUp().then(async isUp => {
if (!isUp) {
const retryOpts = retry ? {max: 25, backoff: 1000} : {max: 0};
return shell.sh(buildDockerCmd('start', this.scriptsDir))
return shell.sh(buildDockerCmd('start', this.scriptsDir, this.docker))
.catch(err => {
throw Error('Could not automatically start the Docker Daemon. Please manually start it to continue.');
})
Expand Down Expand Up @@ -144,7 +158,11 @@ module.exports = class LandoDaemon {
.then(() => {
// Automatically return true if we are in browsery context and on linux because
// this requires SUDO and because the daemon should always be running on nix
if (this.context !== 'node' && process.platform === 'linux') return Promise.resolve(true);
if (
this.context !== 'node'
&& process.platform === 'linux'
&& !require('../utils/is-wsl-interop')(this.docker)
) return Promise.resolve(true);
// Automatically return if we are on Windows or Darwin because we don't
// ever want to automatically turn the VM off since users might be using
// D4M/W for other things.
Expand All @@ -154,10 +172,14 @@ module.exports = class LandoDaemon {
//
// @todo: When/if we can run our own isolated docker daemon we can change
// this back.
if (process.platform === 'win32' || process.platform === 'darwin') return Promise.resolve(true);
if (
process.platform === 'win32'
|| process.platform === 'darwin'
|| require('../utils/is-wsl-interop')(this.docker)
) return Promise.resolve(true);
// Shut provider down if its status is running.
return this.isUp(this.log, this.cache, this.docker).then(isUp => {
if (isUp) return shell.sh(buildDockerCmd('stop', this.scriptsDir), {mode: 'collect'});
if (isUp) return shell.sh(buildDockerCmd('stop', this.scriptsDir, this.docker), {mode: 'collect'});
})
// Wrap errors.
.catch(err => {
Expand Down Expand Up @@ -191,7 +213,7 @@ module.exports = class LandoDaemon {
return Promise.resolve(true);
})
.catch(error => {
log.debug('engine is down with error %j', error);
log.debug('engine is down with error %s', error.message);
return Promise.resolve(false);
});
};
Expand Down
69 changes: 69 additions & 0 deletions scripts/docker-desktop-start.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
$ErrorActionPreference = "Stop"

# Lando is impatient and will keep trying to start Docker Desktop if it's not ready.
# Using a mutex to ensure only one instance of this script runs at a time.
$mutexName = "Global\DockerDesktopStartLock"
$mutex = New-Object System.Threading.Mutex($false, $mutexName)
$lockAcquired = $false

try {
$lockAcquired = $mutex.WaitOne(0, $false)
if (-not $lockAcquired) {
Write-Output "Another instance of the script is already starting Docker Desktop."
exit 0
}

$AppName = "Docker Desktop"
$app = "shell:AppsFolder\$((Get-StartApps $AppName | Select-Object -First 1).AppId)"
$startMenuPath = [System.Environment]::GetFolderPath("CommonStartMenu")
$shortcutPath = Join-Path $startMenuPath "Docker Desktop.lnk"
$programFiles = [System.Environment]::GetFolderPath("ProgramFiles")
$exePath = Join-Path $programFiles "Docker\Docker\Docker Desktop.exe"

if (Get-Process -Name "Docker Desktop" -ErrorAction SilentlyContinue) {
Write-Output "Docker Desktop is already running."
exit 0
}

function Start-App($path) {
Write-Debug "Attempting to start $path"
if (Test-Path $path) {
Start-Process $path
return $true
}
return $false
}

# Try to start via the App, Start Menu shortcut, then Program Files
if (!(Start-App $app) -and !(Start-App $shortcutPath) -and !(Start-App $exePath)) {
Write-Output "Docker Desktop could not be started. Please check the installation."
exit 1
}

# Wait for Docker Desktop to start (Lando only waits 25 seconds before giving up)
$timeout = 25
for ($i = $timeout; $i -gt 0; $i--) {
if (($i % 5) -eq 0) { Write-Debug "Waiting for Docker Desktop to start ($i seconds remaining)" }
Start-Sleep -Seconds 1

if (Get-Process -Name "Docker Desktop" -ErrorAction SilentlyContinue) {
try {
docker info -f '{{.ServerVersion}}' 2>$null | Out-Null
Write-Host "Docker Desktop is running."
break
} catch {
# Ignore error
}
}
}
}
catch {
Write-Error "An error occurred: $_"
exit 1
}
finally {
if ($lockAcquired) {
$mutex.ReleaseMutex()
}
$mutex.Dispose()
}
1 change: 1 addition & 0 deletions utils/get-config-defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const defaultConfig = options => ({
platform: os.platform(),
release: os.release(),
arch: os.arch(),
isWsl: os.release().toLowerCase().includes('microsoft'),
},
pluginDirs: [{path: path.join(__dirname, '..'), subdir: 'plugins', namespace: '@lando'}],
plugins: [{name: '@lando/core', path: path.join(__dirname, '..'), type: 'local'}],
Expand Down
11 changes: 11 additions & 0 deletions utils/is-wsl-interop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

const fs = require('fs');
const os = require('os');

// Checks to see if Docker is installed via WSL/Windows interop.
module.exports = engineBin => {
const isWsl = os.release().toLowerCase().includes('microsoft');
// Docker Desktop for Windows keeps the .exe in the same directory as the WSL binary.
return isWsl && fs.existsSync(`${engineBin}.exe`);
};
Loading