Skip to content

Commit

Permalink
Merge pull request #164 from lando/start-docker-desktop
Browse files Browse the repository at this point in the history
Windows/WSL2 Interoperability Things
  • Loading branch information
pirog authored May 9, 2024
2 parents 8132b26 + 187cd47 commit 5ca1b26
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 17 deletions.
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`);
};

0 comments on commit 5ca1b26

Please sign in to comment.