Skip to content

Commit

Permalink
fix(compose): Fix compose file path resolution as it is copied into the
Browse files Browse the repository at this point in the history
lando directory and be able to set app mount user and use docker compose v2 seperator

Refs: lando/lando#3373
  • Loading branch information
florianPat committed Mar 29, 2024
1 parent eb24d09 commit 6da7cd2
Show file tree
Hide file tree
Showing 15 changed files with 128 additions and 27 deletions.
7 changes: 6 additions & 1 deletion hooks/app-run-v3-build-steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,15 @@ module.exports = async (app, lando) => {

// get v3 buildable services
const buildServices = _.get(app, 'opts.services', app.services);
const dockerComposeServices = _(app.info)
.filter(info => info.type === 'docker-compose')
.map(info => info.service)
.value();
const buildV3Services = _(app.parsedV3Services)
.filter(service => _.includes(buildServices, service.name))
.map(service => service.name)
.value();
.value()
.concat(dockerComposeServices);
app.log.debug('going to build v3 services if applicable', buildV3Services);

// Make sure containers for this app exist; if they don't and we have build locks, we need to kill them
Expand Down
68 changes: 56 additions & 12 deletions lib/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ module.exports = class App {
* @alias app.name
*/
this.name = require('../utils/slugify')(name);
this.project = require('../utils/docker-composify')(this.name);
this.project = this.name;
this._serviceApi = 3;
this._config = lando.config;
this._defaultService = 'appserver';
Expand Down Expand Up @@ -272,14 +272,21 @@ 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.
Expand All @@ -292,16 +299,17 @@ module.exports = class App {
* @event pre_init
* @property {App} app The app instance.
*/
return loadPlugins(this, this._lando).then(() => this.events.emit('pre-init', this))
.then(() => loadPlugins(this, this._lando))

.then(() => this.events.emit('pre-init', this))
// Actually assemble this thing so its ready for that engine
.then(() => {
// Get all the services
this.services = require('../utils/get-app-services')(this.composeData);
// Merge whatever we have thus far together
this.info = require('../utils/get-app-info-defaults')(this);
// finally just make a list of containers
const separator = _.get(this, '_config.orchestratorSeparator', '_');
const separator = _.get(this, '_config.orchestratorSeparator', '-');
this.containers = _(this.info)
.map(service => ([service.service, `${this.project}${separator}${service.service}${separator}1`]))
.fromPairs()
Expand All @@ -322,7 +330,43 @@ module.exports = class App {
// Front load our app mounts
this.add(new this.ComposeService('mounts', {}, {services: require('../utils/get-app-mounts')(this)}), true);
// Then front load our globals
this.add(new this.ComposeService('globals', {}, {services: require('../utils/get-app-globals')(this)}), true);
const globalsData = _.merge(
{services: require('../utils/get-app-globals')(this)},
{
services:
_.omitBy(
_.zipObject(_.keys(this.config.services), _.map(
this.config.services,
service => _.omitBy(
{
environment: _.omitBy({
LANDO_WEBROOT_USER: service.api === 4 ? service.user : service.meUser,
LANDO_WEBROOT_GROUP: service.api === 4 ? service.user : service.meUser,
LANDO_MOUNT: service.api === 4 ? service.working_dir ?? service.appMount : service.appMount,
}, _.isNil),
},
_.isEmpty,
),
)),
_.isEmpty,
),
},
);
_.forEach(globalsData.services, (service, name) => {
if ('docker-compose' !== _.find(this.info, {service: name})?.type) {
return;
}
service.volumes.push(`${this._config.userConfRoot}:/lando:cached`);

if (!this.config.services[name]?.ssl) {
return;
}

service.volumes.push(
`${path.join(path.join(this._config.userConfRoot, 'scripts'), 'add-cert.sh')}:/scripts/000-add-cert.sh`,
);
});
this.add(new this.ComposeService('globals', {}, globalsData), true);
// Take the big dump of all our compose stuff
this.compose = require('../utils/dump-compose-data')(this.composeData, this._dir);

Expand Down
7 changes: 7 additions & 0 deletions lib/compose.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const composeFlags = {
rm: '--rm',
timestamps: '--timestamps',
volumes: '-v',
outputFilePath: '-o',
};

// Default options nad things
Expand All @@ -33,6 +34,7 @@ const defaultOptions = {
pull: {},
rm: {force: true, volumes: true},
up: {background: true, noRecreate: true, recreate: false, removeOrphans: true},
config: {},
};

/*
Expand Down Expand Up @@ -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);
22 changes: 21 additions & 1 deletion lib/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ module.exports = class Engine {
this.dockerInstalled = this.daemon.docker !== false;

// set the compose separator
this.separator = _.get(config, 'orchestratorSeparator', '_');
this.separator = _.get(config, 'orchestratorSeparator', '-');

// Grab the supported ranges for our things
this.supportedVersions = config.dockerSupportedVersions;
Expand Down Expand Up @@ -485,5 +485,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);
};
};

2 changes: 2 additions & 0 deletions lib/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
3 changes: 2 additions & 1 deletion plugins/proxy/builders/_proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ const getProxy = ({proxyCommand, proxyPassThru, proxyDomain, userConfRoot, versi
return {
services: {
proxy: {
image: 'traefik:2.2.0',
image: 'traefik:2.10.7',
command: proxyCommand.join(' '),
environment: {
LANDO_APP_PROJECT: '_lando_',
LANDO_EXTRA_NAMES: `DNS.100 = *.${proxyDomain}`,
LANDO_PROXY_CONFIG_FILE: '/proxy_config/proxy.yaml',
LANDO_PROXY_PASSTHRU: _.toString(proxyPassThru),
LANDO_VERSION: version,
LANDO_DOMAIN: proxyDomain,
},
networks: ['edge'],
volumes: [
Expand Down
2 changes: 1 addition & 1 deletion plugins/proxy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const defaultConfig = {
'/entrypoint.sh',
'--log.level=DEBUG',
'--api.insecure=true',
'--api.dashboard=false',
'--api.dashboard=true',
'--providers.docker=true',
'--entrypoints.https.address=:443',
'--entrypoints.http.address=:80',
Expand Down
2 changes: 1 addition & 1 deletion tasks/ssh.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ module.exports = (lando, app) => {
// prefer appmount
if (config.appMount) opts[1] = config.appMount;
// fallback to working dir if available
if (!config.appMount && _.has(config, 'config.working_dir')) opts[0] = config.config.working_dir;
if (!config.appMount && _.has(config, 'working_dir')) opts[0] = config.working_dir;
}

// continue
Expand Down
2 changes: 1 addition & 1 deletion test/get-config-defaults.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('get-config-defaults', () => {
expect(_.hasIn(defaults, 'plugins')).to.equal(true);
expect(_.hasIn(defaults, 'process')).to.equal(true);
expect(_.hasIn(defaults, 'userConfRoot')).to.equal(true);
expect(_.get(defaults, 'orchestratorSeparator')).to.equal('_');
expect(_.get(defaults, 'orchestratorSeparator')).to.equal('-');
expect(_.get(defaults, 'configSources')).to.be.an('array');
});

Expand Down
12 changes: 11 additions & 1 deletion utils/get-app-info-defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ const _ = require('lodash');

// adds required methods to ensure the lando v3 debugger can be injected into v4 things
module.exports = app => _(app.services)
.map(service => ({service, urls: [], type: 'docker-compose', healthy: true}))
.map(service => _.merge(
{service, urls: [], type: 'docker-compose', healthy: true},
_.omitBy(
{
meUser: _.get(app.config.services, service)?.meUser,
appMount: _.get(app.config.services, service)?.appMount,
hasCerts: _.get(app.config.services, service)?.ssl,
},
_.isNil,
),
))
.map(service => _.merge({}, service, _.find(app.info, {service: service.service})))
.value();
2 changes: 1 addition & 1 deletion utils/get-app-mounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion utils/get-config-defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const os = require('os');

// Default config
const defaultConfig = options => ({
orchestratorSeparator: '_',
orchestratorSeparator: '-',
orchestratorVersion: '2.24.6',
configSources: [],
disablePlugins: [],
Expand Down
4 changes: 2 additions & 2 deletions utils/get-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ module.exports = (name, info = []) => {
if (!_.find(info, {service: name})) return 'www-data';
// otherwise get the service
const service = _.find(info, {service: name});
// if this is a "no-api" service eg type "docker-compose" also return www-data
if (!service.api && service.type === 'docker-compose') return 'www-data';
// if this is a "no-api" service eg type "docker-compose" return meUser or www-data as default
if (!service.api && service.type === 'docker-compose') return service.meUser || 'www-data';
// otherwise return different things based on the api
return service.api === 4 ? service.user || 'www-data' : service.meUser || 'www-data';
};
18 changes: 15 additions & 3 deletions utils/load-compose-files.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
2 changes: 1 addition & 1 deletion utils/parse-v3-services.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ module.exports = (config, app) => _(config)
app: app.name,
confDest: path.join(app._config.userConfRoot, 'config', service.type.split(':')[0]),
data: `data_${service.name}`,
home: app._config.home,
home: app.config.home || app._config.home,
project: app.project,
root: app.root,
type: service.type.split(':')[0],
Expand Down

0 comments on commit 6da7cd2

Please sign in to comment.