From 9d594c463de7c3acd647fb9459161a3239888a61 Mon Sep 17 00:00:00 2001 From: florianPat Date: Sun, 3 Dec 2023 12:14:09 +0100 Subject: [PATCH] fix(compose): Fix compose file path resolution as it is copied into the lando directory Refs: https://github.com/lando/lando/issues/3373 --- lib/app.js | 49 ++++++++++++++++++++++--------------- lib/compose.js | 7 ++++++ lib/engine.js | 20 +++++++++++++++ lib/router.js | 2 ++ utils/get-app-mounts.js | 2 +- utils/load-compose-files.js | 18 +++++++++++--- 6 files changed, 74 insertions(+), 24 deletions(-) diff --git a/lib/app.js b/lib/app.js index 6af6624cf..e02b99097 100644 --- a/lib/app.js +++ b/lib/app.js @@ -272,27 +272,36 @@ module.exports = class App { // We should only need to initialize once, if we have just go right to app ready if (this.initialized) return this.events.emit('ready', this); // Get compose data if we have any, otherwise set to [] - const composeFiles = require('../utils/load-compose-files')(_.get(this, 'config.compose', []), this.root); - this.composeData = [new this.ComposeService('compose', {}, ...composeFiles)]; - // Validate and set env files - this.envFiles = require('../utils/normalize-files')(_.get(this, 'config.env_file', []), this.root); - // Log some things - this.log.verbose('initiatilizing app at %s...', this.root); - this.log.silly('app has config', this.config); + return require('../utils/load-compose-files')( + _.get(this, 'config.compose', []), + this.root, + this._dir, + (composeFiles, outputFilePath) => + this.engine.getComposeConfig({compose: composeFiles, project: this.project, outputFilePath}), + ) + + .then(composeFile => { + this.composeData = [new this.ComposeService('compose', {}, composeFile)]; + // Validate and set env files + this.envFiles = require('../utils/normalize-files')(_.get(this, 'config.env_file', []), this.root); + // Log some things + this.log.verbose('initiatilizing app at %s...', this.root); + this.log.silly('app has config', this.config); - /** - * Event that allows altering of the app object right before it is - * initialized. - * - * Note that this is a global event so it is invoked with `lando.events.on` - * not `app.events.on` See example below: - * - * @since 3.0.0 - * @alias app.events:pre-init - * @event pre_init - * @property {App} app The app instance. - */ - return loadPlugins(this, this._lando).then(() => this.events.emit('pre-init', this)) + /** + * Event that allows altering of the app object right before it is + * initialized. + * + * Note that this is a global event so it is invoked with `lando.events.on` + * not `app.events.on` See example below: + * + * @since 3.0.0 + * @alias app.events:pre-init + * @event pre_init + * @property {App} app The app instance. + */ + return loadPlugins(this, this._lando).then(() => this.events.emit('pre-init', this)) + }) // Actually assemble this thing so its ready for that engine .then(() => { diff --git a/lib/compose.js b/lib/compose.js index 1c09a3ace..cd2890dae 100644 --- a/lib/compose.js +++ b/lib/compose.js @@ -20,6 +20,7 @@ const composeFlags = { rm: '--rm', timestamps: '--timestamps', volumes: '-v', + outputFilePath: '-o', }; // Default options nad things @@ -33,6 +34,7 @@ const defaultOptions = { pull: {}, rm: {force: true, volumes: true}, up: {background: true, noRecreate: true, recreate: false, removeOrphans: true}, + config: {}, }; /* @@ -127,3 +129,8 @@ exports.start = (compose, project, opts = {}) => buildShell('up', project, compo * Run docker compose kill */ exports.stop = (compose, project, opts = {}) => buildShell('kill', project, compose, opts); + +/* + * Run docker compose config + */ +exports.config = (compose, project, opts = {}) => buildShell('config', project, compose, opts); diff --git a/lib/engine.js b/lib/engine.js index 4e25b78fc..c98f27cc1 100644 --- a/lib/engine.js +++ b/lib/engine.js @@ -479,5 +479,25 @@ module.exports = class Engine { // stop return this.engineCmd('stop', data); }; + + /** + * Get dumped docker compose config for compose files from project + * using a `compose` object with `{compose: compose, project: project, opts: opts}` + * + * @since 3.0.0 + * @param {Object} data Config needs a service within a compose context + * @param {Array} data.compose An Array of paths to Docker compose files + * @param {String} data.project A String of the project name (Usually this is the same as the app name) + * @param {String} [data.outputFilePath='/path/to/file.yml'] String to output path + * @param {Object} [data.opts] Options + * @return {Promise} A Promise. + * @example + * return lando.engine.stop(app); + */ + getComposeConfig(data) { + data.opts = {cmd: ['-o', data.outputFilePath]}; + delete data.outputFilePath; + return this.engineCmd('config', data); + }; }; diff --git a/lib/router.js b/lib/router.js index 6c27d1487..a6a7c67f9 100644 --- a/lib/router.js +++ b/lib/router.js @@ -131,3 +131,5 @@ exports.start = (data, compose) => retryEach(data, datum => compose('start', dat exports.stop = (data, compose, docker) => retryEach(data, datum => { return (datum.compose) ? compose('stop', datum) : docker.stop(getContainerId(datum)); }); + +exports.config = (data, compose) => retryEach(data, datum => compose('config', datum)); diff --git a/utils/get-app-mounts.js b/utils/get-app-mounts.js index 694ffedb3..521b4af70 100644 --- a/utils/get-app-mounts.js +++ b/utils/get-app-mounts.js @@ -6,7 +6,7 @@ module.exports = app => _(app.services) // Objectify .map(service => _.merge({name: service}, _.get(app, `config.services.${service}`, {}))) // Set the default - .map(config => _.merge({}, config, {app_mount: _.get(config, 'app_mount', 'cached')})) + .map(config => _.merge({}, config, {app_mount: _.get(config, 'app_mount', app.config.app_mount || 'cached')})) // Filter out disabled mountes .filter(config => config.app_mount !== false && config.app_mount !== 'disabled') // Combine together diff --git a/utils/load-compose-files.js b/utils/load-compose-files.js index f13bf4cec..a685f79dd 100644 --- a/utils/load-compose-files.js +++ b/utils/load-compose-files.js @@ -2,8 +2,20 @@ const _ = require('lodash'); const Yaml = require('./../lib/yaml'); +const path = require('path'); const yaml = new Yaml(); +const fs = require('fs'); -module.exports = (files, dir) => _(require('./normalize-files')(files, dir)) - .map(file => yaml.load(file)) - .value(); +// This just runs `docker compose --project-directory ${dir} config -f ${files} --output ${outputPaths}` to +// make all paths relative to the lando config root +module.exports = async (files, dir, landoComposeConfigDir, outputConfigFunction) => { + const composeFilePaths = _(require('./normalize-files')(files, dir)).value(); + const outputFile = path.join(landoComposeConfigDir, 'resolved-compose-config.yml'); + + fs.mkdirSync(path.dirname(outputFile), {recursive: true}); + await outputConfigFunction(composeFilePaths, outputFile); + const result = yaml.load(outputFile); + fs.unlinkSync(outputFile); + + return result; +};