diff --git a/CHANGELOG.md b/CHANGELOG.md index 269c404..28ee611 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## {{ UNRELEASED_VERSION }} - [{{ UNRELEASED_DATE }}]({{ UNRELEASED_LINK }}) +* Updated `lando setup` to install `docker-buildx` if missing + ## v4.0.0-unstable.8 - [December 9, 2025](https://github.com/lando/core-next/releases/tag/v4.0.0-unstable.8) * Updated to prep for `bunification` part 2 diff --git a/examples/setup-linux-buildx/README.md b/examples/setup-linux-buildx/README.md new file mode 100644 index 0000000..bae79a8 --- /dev/null +++ b/examples/setup-linux-buildx/README.md @@ -0,0 +1,33 @@ +# Setup Linux Buildx Tests + +This example exists primarily to test that `lando setup` correctly installs buildx when it is not present. + +* [lando setup](https://docs.lando.dev/cli/setup.html) + +## Verification commands + +Run the following commands to validate things are rolling as they should. + +```bash +# Should dogfood the core plugin we are testing against +lando plugin-add "@lando/core@file:../.." + +# Should have buildx installed by default on GitHub runners +docker buildx version + +# Should be able to remove buildx +rm -f ~/.docker/cli-plugins/docker-buildx +sudo rm -f /usr/local/lib/docker/cli-plugins/docker-buildx +sudo rm -f /usr/local/libexec/docker/cli-plugins/docker-buildx +sudo rm -f /usr/lib/docker/cli-plugins/docker-buildx +sudo rm -f /usr/libexec/docker/cli-plugins/docker-buildx + +# Should confirm buildx is no longer available +(docker buildx version 2>&1 || true) | tee >(cat) | grep -i "not found\|is not a docker command\|unknown command" + +# Should be able to run lando setup and have it reinstall buildx +lando setup -y --skip-common-plugins + +# Should have buildx installed again +docker buildx version +``` diff --git a/hooks/lando-setup-buildx.js b/hooks/lando-setup-buildx.js new file mode 100644 index 0000000..291afde --- /dev/null +++ b/hooks/lando-setup-buildx.js @@ -0,0 +1,91 @@ +'use strict'; + +const axios = require('../utils/get-axios')(); +const fs = require('fs'); +const path = require('path'); + +/* + * Helper to get buildx download url + */ +const getBuildxDownloadUrl = (version = '0.30.1') => { + const arch = process.arch === 'arm64' ? 'arm64' : 'amd64'; + + switch (process.platform) { + case 'darwin': + return `https://github.com/docker/buildx/releases/download/v${version}/buildx-v${version}.darwin-${arch}`; + case 'linux': + return `https://github.com/docker/buildx/releases/download/v${version}/buildx-v${version}.linux-${arch}`; + case 'win32': + return `https://github.com/docker/buildx/releases/download/v${version}/buildx-v${version}.windows-${arch}.exe`; + } +}; + +/* + * Helper to get buildx download destination + */ +const getBuildxDownloadDest = home => { + const dir = path.join(home, '.docker', 'cli-plugins'); + switch (process.platform) { + case 'linux': + case 'darwin': + return path.join(dir, 'docker-buildx'); + case 'win32': + return path.join(dir, 'docker-buildx.exe'); + } +}; + +module.exports = async (lando, options) => { + const debug = require('../utils/debug-shim')(lando.log); + const {color} = require('listr2'); + + // get stuff from config/opts + const {home} = lando.config; + const {buildx} = options; + + // if buildx is set to false allow it to be skipped + if (buildx === false) return; + + const dest = getBuildxDownloadDest(home); + const url = getBuildxDownloadUrl(buildx); + + options.tasks.push({ + title: `Downloading buildx`, + id: 'setup-buildx', + dependsOn: ['setup-build-engine'], + description: '@lando/buildx (docker-buildx)', + version: `Docker Buildx v${buildx}`, + hasRun: async () => { + try { + await require('../utils/run-command')('docker', ['buildx', 'version'], {debug}); + return true; + } catch { + return false; + } + }, + canRun: async () => { + // throw error if we cannot ping the download link + await axios.head(url); + // true if we get here + return true; + }, + task: async (ctx, task) => new Promise((resolve, reject) => { + // ensure the cli-plugins directory exists + fs.mkdirSync(path.dirname(dest), {recursive: true}); + + const download = require('../utils/download-x')(url, {debug, dest, test: ['version']}); + // success + download.on('done', data => { + task.title = `Installed buildx (Docker Buildx) to ${dest}`; + resolve(data); + }); + // handle errors + download.on('error', error => { + reject(error); + }); + // update title to reflect download progress + download.on('progress', progress => { + task.title = `Downloading buildx ${color.dim(`[${progress.percentage}%]`)}`; + }); + }), + }); +}; diff --git a/index.js b/index.js index 090bed0..053f669 100644 --- a/index.js +++ b/index.js @@ -97,6 +97,9 @@ module.exports = async lando => { // ensure we setup docker-compose if needed lando.events.once('pre-setup', async options => await require('./hooks/lando-setup-orchestrator')(lando, options)); + // ensure we setup buildx if needed + lando.events.once('pre-setup', async options => await require('./hooks/lando-setup-buildx')(lando, options)); + // ensure we setup landonet lando.events.once('pre-setup', async options => await require('./hooks/lando-setup-landonet')(lando, options)); diff --git a/tasks/setup.js b/tasks/setup.js index cd097a5..0d5cae1 100644 --- a/tasks/setup.js +++ b/tasks/setup.js @@ -80,6 +80,11 @@ module.exports = lando => { default: defaults.orchestrator, string: true, }, + 'buildx': { + describe: 'Sets the version of buildx to install', + default: defaults.buildx, + string: true, + }, 'plugin': { describe: 'Sets additional plugin(s) to install', default: require('../utils/parse-to-plugin-strings')(defaults.plugins), @@ -129,6 +134,7 @@ module.exports = lando => { [--build-engine ] [--build-engine-accept-license] [--orchestrator ] + [--buildx ] [--plugin ...] [--skip-common-plugins] [--skip-install-ca] diff --git a/utils/get-config-defaults.js b/utils/get-config-defaults.js index ead6181..18d10c1 100644 --- a/utils/get-config-defaults.js +++ b/utils/get-config-defaults.js @@ -50,6 +50,7 @@ const defaultConfig = options => ({ // @TODO: orchestrator works a bit differently because it predates lando.setup() we set it elsewhere setup: { buildEngine: getBuildEngineVersion(process.landoPlatform ?? process.platform), + buildx: '0.30.1', buildEngineAcceptLicense: !require('is-interactive')(), commonPlugins: { '@lando/acquia': 'latest',