diff --git a/CHANGELOG.md b/CHANGELOG.md index d987118..f69c0c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## {{ UNRELEASED_VERSION }} - [{{ UNRELEASED_DATE }}]({{ UNRELEASED_LINK }}) +* Improved the `php` service builder to mount a unique scripts directory per service to prevent version conflicts. + ## v1.7.0 - [January 8, 2025](https://github.com/lando/php/releases/tag/v1.7.0) * Added logic to allow default `composer` version to be set based on PHP version. diff --git a/app.js b/app.js new file mode 100644 index 0000000..57aa379 --- /dev/null +++ b/app.js @@ -0,0 +1,13 @@ +/** + * Event hooks for the PHP plugin + * + * @param {Object} app - The Lando app instance + * @param {Object} lando - The Lando instance + * @return {void} + */ +module.exports = (app, lando) => { + // Remove the service config directory on appdestroy + app.events.on('post-destroy', async () => { + return await require('../hooks/app-purge-service-dirs')(app, lando); + }); +}; diff --git a/builders/php.js b/builders/php.js index a40e797..6398f73 100644 --- a/builders/php.js +++ b/builders/php.js @@ -172,14 +172,29 @@ module.exports = { parent: '_appserver', builder: (parent, config) => class LandoPhp extends parent { constructor(id, options = {}, factory) { - const debug = _.get(options, '_app._lando').log.debug; + const lando = _.get(options, '_app._lando'); + const logger = lando.log; + const fs = require('fs'); // Merge the user config onto the default options options = parseConfig(_.merge({}, config, options)); // Get the semver of the PHP version, NULL if we cannot parse it const phpSemver = semver.coerce(options.version); - phpSemver && debug('Parsed PHP semantic version: %s', phpSemver); + phpSemver && logger.debug('Parsed PHP semantic version: %s', phpSemver); + + // Move the script to the confDir and make executable. + const pluginScriptsDir = path.join(__dirname, '../scripts'); + if (fs.existsSync(pluginScriptsDir)) { + const appConfDir = require('../utils/get-appconf-dir')(lando, options._app); + const serviceScriptsDir = path.join(appConfDir, `service-php-${options.name}`, 'scripts'); + + const scriptsDir = lando.utils.moveConfig(pluginScriptsDir, serviceScriptsDir); + logger.debug('automoved scripts from %s to %s', pluginScriptsDir, scriptsDir); + + // Mount the scripts directory at /opt/lando-service/scripts + options.volumes.push(`${scriptsDir}:/opt/lando-service/scripts`); + } // Mount our default php config options.volumes.push(`${options.confDest}/${options.defaultFiles._php}:${options.remoteFiles._php}`); @@ -239,8 +254,8 @@ module.exports = { // Install the desired composer version as the first `build_internal` build step if (options.composer_version) { - debug('Installing composer version %s', options.composer_version); - const commands = [`/helpers/install-composer.sh ${options.composer_version}`]; + logger.debug('Installing composer version %s', options.composer_version); + const commands = [`/opt/lando-service/scripts/install-composer.sh ${options.composer_version}`]; const firstStep = true; addBuildStep(commands, options._app, options.name, 'build_internal', firstStep); } diff --git a/examples/5.6/.lando.yml b/examples/5.6/.lando.yml index dde28d1..9aff6a0 100644 --- a/examples/5.6/.lando.yml +++ b/examples/5.6/.lando.yml @@ -5,6 +5,9 @@ events: services: defaults: type: php:5.6 + composer_lts: + type: php:5.6 + composer_version: 2.2 cli: type: php:5.6 composer_version: 1 diff --git a/examples/5.6/README.md b/examples/5.6/README.md index 89bf257..172a484 100644 --- a/examples/5.6/README.md +++ b/examples/5.6/README.md @@ -54,6 +54,9 @@ lando exec defaults -- php -i | grep "memory_limit" | grep -e "-1" # Should not enable xdebug by default lando exec defaults -- php -m | grep xdebug || echo $? | grep 1 +# Should install composer 2.2.x if composer_version is set to 2.2 +lando exec composer_lts -- composer --version --no-ansi | tee >(cat 1>&2) | grep -q "Composer version 2.2." + # Should have a PATH_INFO and PATH_TRANSLATED SERVER vars lando exec custom_nginx -- curl https://localhost | grep SERVER | grep PATH_INFO lando exec custom_nginx -- curl https://localhost | grep SERVER | grep PATH_TRANSLATED diff --git a/hooks/app-purge-service-dirs.js b/hooks/app-purge-service-dirs.js new file mode 100644 index 0000000..11060ad --- /dev/null +++ b/hooks/app-purge-service-dirs.js @@ -0,0 +1,28 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +/** + * Removes all PHP service directories for an app + * + * @param {Object} app - The Lando app instance + * @param {Object} lando - The Lando instance + * @return {Promise} Promise that resolves when directories are removed + * @throws {Error} If unable to get services directory + */ +module.exports = async (app, lando) => { + try { + const servicesDir = require('../utils/get-services-dir')(lando, app); + app.log.debug('removing php service directories...'); + // Get all subdirectories starting with 'php-' and remove them + if (fs.existsSync(servicesDir)) { + fs.readdirSync(servicesDir) + .filter(dir => dir.startsWith('php-')) + .forEach(dir => { + const phpServiceDir = path.join(servicesDir, dir); + lando.utils.remove(phpServiceDir); + }); + } + } catch {} +}; diff --git a/utils/get-appconf-dir.js b/utils/get-appconf-dir.js new file mode 100644 index 0000000..f4707cd --- /dev/null +++ b/utils/get-appconf-dir.js @@ -0,0 +1,17 @@ +'use strict'; + +const path = require('path'); + +/** + * Get the app's config directory path + * @param {Object} lando - The Lando instance + * @param {Object} app - The application object + * @return {string} The full path to the app's config directory + * @throws {Error} If required parameters are missing + */ +module.exports = (lando, app) => { + if (!lando?.config?.userConfRoot) throw new Error('Invalid Lando configuration'); + if (!app?.name || !app?.id) throw new Error('Invalid app configuration'); + + return path.join(lando.config.userConfRoot, 'apps', `${app.name}-${app.id}`); +};