From 3d3181ebf4dcedfdf7fa8409cd02505efc0f3908 Mon Sep 17 00:00:00 2001 From: cha0s Date: Wed, 2 Mar 2022 00:54:53 -0600 Subject: [PATCH] feat: basic auto-dockerization --- packages/core/src/server/flecks.js | 15 ++-- packages/docker/src/commands.js | 132 +++++++++++++++++++++++++++++ packages/docker/src/server.js | 8 ++ packages/redis/src/containers.js | 7 +- 4 files changed, 154 insertions(+), 8 deletions(-) create mode 100644 packages/docker/src/commands.js diff --git a/packages/core/src/server/flecks.js b/packages/core/src/server/flecks.js index 2c5aa9f..6a9d68b 100644 --- a/packages/core/src/server/flecks.js +++ b/packages/core/src/server/flecks.js @@ -45,13 +45,7 @@ export default class ServerFlecks extends Flecks { Object.keys(this.flecks) .sort((l, r) => (l < r ? 1 : -1)) .forEach((fleck) => { - const prefix = `FLECKS_ENV_${ - fleck - // - `@flecks/core` -> `FLECKS_CORE` - .replace(/[^a-zA-Z0-9]/g, '_') - .replace(/_*(.*)_*/, '$1') - .toUpperCase() - }`; + const prefix = `FLECKS_ENV_${this.constructor.environmentalize(fleck).toUpperCase()}`; keys .filter((key) => key.startsWith(`${prefix}_`) && -1 === seen.indexOf(key)) .map((key) => { @@ -360,6 +354,13 @@ export default class ServerFlecks extends Flecks { }); } + static environmentalize(key) { + return key + // - `@flecks/core` -> `FLECKS_CORE` + .replace(/[^a-zA-Z0-9]/g, '_') + .replace(/_*(.*)_*/, '$1'); + } + fleckIsAliased(fleck) { return this.constructor.fleckIsAliased(this.resolver, fleck); } diff --git a/packages/docker/src/commands.js b/packages/docker/src/commands.js new file mode 100644 index 0000000..0b96e83 --- /dev/null +++ b/packages/docker/src/commands.js @@ -0,0 +1,132 @@ +import {spawn} from 'child_process'; +import {writeFile} from 'fs/promises'; +import {join, relative} from 'path'; + +import {D, dumpYml} from '@flecks/core'; + +const { + FLECKS_CORE_ROOT = process.cwd(), +} = process.env; + +const debug = D('@flecks/docker/commands'); + +export default (program, flecks) => { + const commands = {}; + commands.compose = { + options: [ + ['-r, --run', 'run docker-compose'], + ], + description: 'generate a docker compose file', + action: async (opts) => { + const { + run, + } = opts; + const output = join(FLECKS_CORE_ROOT, 'dist'); + const lines = [ + "version: '3'", + 'services:', + '', + ]; + const services = { + app: { + build: { + context: '..', + dockerfile: 'dist/Dockerfile', + }, + environment: { + FLECKS_ENV_FLECKS_DOCKER_SERVER_enabled: 'false', + }, + volumes: [ + '../node_modules:/var/www/node_modules', + ], + }, + }; + const containers = flecks.invoke('@flecks/docker/containers'); + ( + await Promise.all( + Object.entries(containers) + .map(async ([fleck, config]) => { + Object.entries(await config) + .forEach(([key, config]) => { + services[key] = {image: config.image, environment: {}}; + }); + return [ + `FLECKS_ENV_${flecks.constructor.environmentalize(fleck).toUpperCase()}`, + config, + ]; + }), + ) + ) + // Set environment. + .forEach(([prefix, config]) => { + Object.values(config) + .forEach((config) => { + Object.entries(config.environment || {}) + .forEach(([configService, environment]) => { + Object.entries(environment || {}) + .forEach(([key, value]) => { + const qualified = 'app' === configService ? `${prefix}_${key}` : key; + services[configService].environment[qualified] = value; + }); + }); + }); + }); + Object.entries(services) + .forEach(([key, service]) => { + lines.push( + ...dumpYml({[key]: service}) + .split('\n') + .map((line) => ` ${line}`), + ); + }); + const composeFile = join(output, 'docker-compose.yml'); + debug('writing %s...', composeFile); + await writeFile( + composeFile, + lines + .map((line) => ('' === line.trim() ? '' : line)) + .join('\n'), + ); + const dockerFile = join(output, 'Dockerfile'); + debug('writing %s...', dockerFile); + await writeFile( + dockerFile, + [ + 'FROM node:16', + '', + 'RUN mkdir -p /var/www', + 'WORKDIR /var/www', + 'COPY package.json /var/www', + 'COPY build /var/www/build', + 'COPY dist /var/www/dist', + '', + 'ENV DEBUG=*', + 'ENV NODE_ENV=production', + '', + 'CMD ["node", "./dist/index.js"]', + '', + 'VOLUME /var/www/node_modules', + '', + ].join('\n'), + ); + /* eslint-disable no-console */ + console.log(); + console.log('Outputs:'); + console.log(); + console.group(); + console.log(relative(FLECKS_CORE_ROOT, composeFile)); + console.log(relative(FLECKS_CORE_ROOT, dockerFile)); + console.groupEnd(); + console.log(); + /* eslint-enable no-console */ + if (run) { + spawn( + 'docker-compose', + ['-f', 'dist/docker-compose.yml', 'up', '--build'], + {stdio: 'inherit'}, + ); + } + }, + }; + return commands; +}; diff --git a/packages/docker/src/server.js b/packages/docker/src/server.js index 1212e12..5dfbc83 100644 --- a/packages/docker/src/server.js +++ b/packages/docker/src/server.js @@ -1,10 +1,18 @@ import {Hooks} from '@flecks/core'; +import commands from './commands'; import startContainer from './start-container'; export default { [Hooks]: { + '@flecks/core/config': () => ({ + enabled: true, + }), + '@flecks/core/commands': commands, '@flecks/server/up': async (flecks) => { + if (!flecks.get('@flecks/docker/server.enabled')) { + return; + } const containers = await flecks.invokeReduceAsync('@flecks/docker/containers'); await Promise.all( Object.entries(containers) diff --git a/packages/redis/src/containers.js b/packages/redis/src/containers.js index c849547..a6c4ce1 100644 --- a/packages/redis/src/containers.js +++ b/packages/redis/src/containers.js @@ -1,6 +1,11 @@ export default (flecks) => ({ redis: { - image: 'redis', + environment: { + app: { + host: 'redis', + }, + }, + image: 'redis:6', mount: '/data', ports: {[flecks.get('@flecks/redis/server.port')]: 6379}, },