From 816fc6d80b7286acc35b0e1ae32fe08c65e8af18 Mon Sep 17 00:00:00 2001 From: AVVS Date: Fri, 19 Oct 2018 15:44:20 -0700 Subject: [PATCH] feat: auto_compose, generate docker_compose file via mdeprc --- .mdeprc | 18 ++++- README.md | 33 ++++++--- __tests__/docker-compose.yml | 14 ---- bin/cli.js | 24 +++--- bin/cmds/test.js | 21 ++++++ bin/cmds/test_cmds/auto_compose.js | 113 +++++++++++++++++++++++++++++ bin/cmds/test_cmds/compose.js | 16 +++- bin/cmds/test_cmds/run.js | 2 +- package.json | 4 + templates/redis-sentinel.sh | 26 +++++++ yarn.lock | 15 +++- 11 files changed, 245 insertions(+), 41 deletions(-) delete mode 100644 __tests__/docker-compose.yml create mode 100644 bin/cmds/test_cmds/auto_compose.js create mode 100644 templates/redis-sentinel.sh diff --git a/.mdeprc b/.mdeprc index b25473d..5c8e3cb 100644 --- a/.mdeprc +++ b/.mdeprc @@ -2,5 +2,21 @@ "nycCoverage": false, "test_framework": "jest --coverage --coverageDirectory --detectOpenHandles", "tests": "__tests__/*.js", - "docker_compose": "__tests__/docker-compose.yml" + "auto_compose": true, + "node": "10.12.0", + "tester_flavour": "chrome-tester", + "services": [ + "redisSentinel", + "redisCluster", + "rabbitmq", + "postgres" + ], + "extras": { + "tester": { + "shm_size": "128m", + "environment": { + "CHROME_PATH": "/usr/bin/chromium-browser" + } + } + } } diff --git a/README.md b/README.md index 9ffa1a4..6bcaeda 100644 --- a/README.md +++ b/README.md @@ -84,27 +84,38 @@ Options: ## Test ```bash -bin/cli.js test +cli.js test + +performs tests in docker Commands: - compose installs compose on the system - init adds basic files for testing - run performs testing + cli.js test compose prepares docker-compose file based on config + cli.js test compose installs compose on the system + cli.js test init adds basic files for testing + cli.js test run performs testing Options: --node, -n node version to use when building - [default: "7.8.0"] + [default: "9.3.0"] --env, -E node environment to build for [default: "production"] --project, -p project name where this is used - [default: "makeomatic-deploy"] + [default: "deploy"] + --repository, --repo docker repository to use + [default: "makeomatic"] --version, -v version of the project to build - [default: "1.5.0"] + [default: "0.0.0-development"] --pkg package json path - [default: "/Users/vitaly/projects/makeomatic-deploy/package.json"] - --help Show help [boolean] + [default: "/Users/vitaly/projects/@makeomatic/deploy/package.json"] --docker_compose docker-compose file for testing [string] [default: "./test/docker-compose.yml"] + --auto_compose [boolean] [default: false] + --tester_flavour [string] [default: "tester"] + --extras any extras for tester docker container, will + be merged [string] [default: {}] + --services enable listed services + [array] [choices: "redis", "redisCluster", "redisSentinel", "postgres", + "rabbitmq"] --docker_compose_version, --dcv docker-compose version to use [default: "1.11.2"] --docker_compose_force, --dcf forces to install local copy of @@ -131,4 +142,8 @@ Options: [boolean] [default: false] --arbitrary_exec arbitrary commands to exec in docker tester [array] [default: []] + --pre pre commands to run [array] [default: []] + --nycCoverage set to --no-nycCoverage to disable it + [boolean] [default: true] + --help Show help [boolean] ``` diff --git a/__tests__/docker-compose.yml b/__tests__/docker-compose.yml deleted file mode 100644 index de6dcc4..0000000 --- a/__tests__/docker-compose.yml +++ /dev/null @@ -1,14 +0,0 @@ -version: '3' - -services: - tester: - shm_size: 512m - image: makeomatic/node:10.11.0-chrome-tester - hostname: tester - working_dir: /src - volumes: - - ${PWD}:/src - environment: - NODE_ENV: test - CHROME_PATH: /usr/bin/chromium-browser - command: tail -f /dev/null diff --git a/bin/cli.js b/bin/cli.js index 78fbcd5..8f6f814 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -1,24 +1,22 @@ #!/usr/bin/env node +/* eslint-disable import/no-dynamic-require, no-nested-ternary */ const path = require('path'); const fs = require('fs'); const findUp = require('find-up'); +const readPkg = require('read-pkg'); +const assert = require('assert'); -let parentProject; -try { - // eslint-disable-next-line import/no-dynamic-require - parentProject = require(`${process.cwd()}/package.json`); - if (!parentProject.version) { - throw new Error('package.json missing version'); - } -} catch (e) { - throw new Error(`Must contain package.json in the current dir: ${e.message}`); -} +const parentProject = readPkg.sync(); +assert(parentProject && parentProject.version, 'Must contain package.json in the current dir'); // get configPath if it's there const configPath = findUp.sync(['.mdeprc', '.mdeprc.js', '.mdeprc.json']); -// eslint-disable-next-line import/no-dynamic-require -const config = configPath ? JSON.parse(fs.readFileSync(configPath)) : {}; +const config = configPath + ? configPath.endsWith('.js') + ? require(configPath) + : JSON.parse(fs.readFileSync(configPath)) + : {}; require('yargs') .version(false) @@ -27,7 +25,7 @@ require('yargs') .option('node', { alias: 'n', describe: 'node version to use when building', - default: '9.3.0', + default: '10.12.0', }) .option('env', { alias: 'E', diff --git a/bin/cmds/test.js b/bin/cmds/test.js index 444ce66..4845681 100644 --- a/bin/cmds/test.js +++ b/bin/cmds/test.js @@ -13,6 +13,27 @@ exports.builder = yargs => ( default: './test/docker-compose.yml', normalize: true, }) + .option('auto_compose', { + type: 'boolean', + default: false, + }) + .option('tester_flavour', { + type: 'string', + default: 'tester', + }) + .option('extras', { + description: 'any extras for tester docker container, will be merged', + type: 'string', + default: {}, + coerce(argv) { + return typeof argv === 'string' ? JSON.parse(argv) : argv; + }, + }) + .option('services', { + type: 'array', + description: 'enable listed services', + choices: Object.keys(require('./test_cmds/auto_compose').SERVICE_MAP), + }) .option('docker_compose_version', { alias: 'dcv', describe: 'docker-compose version to use', diff --git a/bin/cmds/test_cmds/auto_compose.js b/bin/cmds/test_cmds/auto_compose.js new file mode 100644 index 0000000..38cc3cd --- /dev/null +++ b/bin/cmds/test_cmds/auto_compose.js @@ -0,0 +1,113 @@ +/* eslint-disable no-use-before-define, no-template-curly-in-string */ +const jsYaml = require('js-yaml'); +const os = require('os'); +const fs = require('fs'); +const hyperid = require('hyperid'); +const merge = require('lodash.merge'); +const path = require('path'); + +const SERVICE_MAP = { + redis, + redisCluster, + redisSentinel, + postgres, + rabbitmq, +}; + +exports.SERVICE_MAP = SERVICE_MAP; +exports.command = 'compose'; +exports.desc = 'prepares docker-compose file based on config'; +exports.handler = (argv) => { + const getId = hyperid({ fixedLength: true, urlSafe: true }); + + // Header of the file + const compose = {}; + compose.version = '3'; + compose.networks = {}; + compose.services = {}; + + // Identification + if (Array.isArray(argv.services) && argv.services.length) { + for (const service of argv.services) { + const ctor = SERVICE_MAP[service]; + if (ctor === undefined) { + throw new Error(`no support for ${service}, please add it to @makeomatic/deploy`); + } + + ctor(compose, argv); + } + } + + // add default tester service + tester(compose, argv); + + // finalize and push out to tmp + const dir = os.tmpdir(); + const filename = `docker-compose.${getId()}.yml`; + const location = `${dir}/${filename}`; + + // write out the file + fs.writeFileSync(location, jsYaml.safeDump(compose)); + + // rewrite location of docker-compose + argv.docker_compose = location; +}; + +/** + * Prepares tester declaration + */ +function tester(compose, argv) { + compose.services.tester = merge({ + image: argv.tester_image || `makeomatic/node:${argv.node}-${argv.tester_flavour}`, + hostname: 'tester', + working_dir: '/src', + volumes: ['${PWD}:/src'], + environment: { + NODE_ENV: 'test', + }, + command: 'tail -f /dev/null', + }, argv.extras.tester); +} + +function redisCluster(compose, argv) { + compose.services.redisCluster = merge({ + image: 'makeomatic/redis-cluster:3.2.9', + hostname: 'redis-cluster', + }, argv.extras.redisCluster); +} + +function redis(compose, argv) { + compose.services.redis = merge({ + image: 'redis:4.0.11-alpine', + hostname: 'redis', + expose: ['6379'], + }, argv.extras.redis); +} + +function redisSentinel(compose, argv) { + redis(compose, argv); + + const entrypoint = path.resolve(__dirname, '../../../templates/redis-sentinel.sh'); + compose.services.redisSentinel = merge({ + image: 'redis:4.0.11-alpine', + hostname: 'redis-sentinel', + expose: ['26379'], + depends_on: ['redis'], + volumes: [`${entrypoint}:/entrypoint.sh:ro`], + command: '/bin/sh /entrypoint.sh redis', + }, argv.extras.redisSentinel); +} + +function postgres(compose, argv) { + compose.services.postgres = merge({ + image: 'postgres:10.4-alpine', + hostname: 'postgres', + }, argv.extras.postgres); +} + +function rabbitmq(compose, argv) { + compose.services.rabbitmq = merge({ + image: 'rabbitmq:3.7.8-management-alpine', + hostname: 'rabbitmq', + }, argv.extras.rabbitmq); +} diff --git a/bin/cmds/test_cmds/compose.js b/bin/cmds/test_cmds/compose.js index 2751805..a494247 100644 --- a/bin/cmds/test_cmds/compose.js +++ b/bin/cmds/test_cmds/compose.js @@ -44,6 +44,13 @@ exports.handler = (argv) => { chmod('+x', compose); } + /** + * Generates dynamic docker-compose file based on the presets + */ + if (argv.auto_compose) { + require('./auto_compose').handler(argv); + } + // add link to compose file argv.compose = ShellString(`${compose} -f ${argv.docker_compose}`); @@ -57,12 +64,17 @@ exports.handler = (argv) => { if (argv.no_cleanup !== true) { echo(`\nAutomatically cleaning up after ${signal}\n`); - exec(`${dockerCompose} stop; true`); exec(`${dockerCompose} down; true`); + + if (argv.auto_compose) { + echo(`rm ${argv.docker_compose}`); + exec(`rm ${argv.docker_compose}`); + } + // force exit now if (signal === 'exit') process.exit(code || 0); } else { - echo(`\nLocal environment detected.\nTo stop containers write:\n\n${dockerCompose} stop;\n${dockerCompose} rm -f -v;\n`); + echo(`\nLocal environment detected.\nTo stop containers write:\n\n${dockerCompose} down;\n`); } } diff --git a/bin/cmds/test_cmds/run.js b/bin/cmds/test_cmds/run.js index 74e4d42..8f25e1e 100644 --- a/bin/cmds/test_cmds/run.js +++ b/bin/cmds/test_cmds/run.js @@ -46,7 +46,7 @@ exports.handler = async (argv) => { const { compose } = argv; // start containers - echo('bringing up containers'); + echo(`bringing up containers via ${compose}`); if (exec(`${compose} up -d`).code !== 0) { echo('failed to start docker containers. Exit 128'); exit(128); diff --git a/package.json b/package.json index 4a28d57..9f7919a 100644 --- a/package.json +++ b/package.json @@ -46,12 +46,16 @@ "find-up": "^3.0.0", "glob": "^7.1.3", "husky": "^1.1.2", + "hyperid": "^1.4.1", "is": "^3.2.1", + "js-yaml": "^3.12.0", "lodash.get": "^4.4.2", + "lodash.merge": "^4.6.1", "lodash.set": "^4.3.2", "ms-conf": "^3.3.1", "npm-path": "^2.0.4", "pino": "^5.8.0", + "read-pkg": "^4.0.1", "rimraf": "^2.6.2", "semantic-release": "15.9.x", "shelljs": "^0.8.2", diff --git a/templates/redis-sentinel.sh b/templates/redis-sentinel.sh new file mode 100644 index 0000000..8237fa7 --- /dev/null +++ b/templates/redis-sentinel.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +# some settings +dir="/etc/redis-sentinel" +configuration="${dir}/redis-sentinel.conf" +hostname=$1 + +if [ ! -f "$configuration" ] +then + mkdir -p $dir + apk --no-cache --quiet add bind-tools + ip=$(dig ${hostname} +short) + echo "sentinel myid 6050271ee52c6a1f3302b44fe6793f2e3e0de356" > $configuration + echo "sentinel monitor mservice $ip 6379 1" >> $configuration + echo "sentinel down-after-milliseconds mservice 60000" >> $configuration + echo "sentinel config-epoch mservice 0" >> $configuration + echo "# Generated by CONFIG REWRITE" >> $configuration + echo "port 26379" >> $configuration + echo "dir \"/data\"" >> $configuration + echo "protected-mode no" >> $configuration + echo "sentinel leader-epoch mservice 4" >> $configuration + echo "sentinel current-epoch 4" >> $configuration + chown -R $(id -u redis):$(id -g redis) $dir +fi + +exec redis-server /etc/redis-sentinel/redis-sentinel.conf --sentinel diff --git a/yarn.lock b/yarn.lock index 00a7459..539e1b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3286,6 +3286,14 @@ husky@^1.1.2: run-node "^1.0.0" slash "^2.0.0" +hyperid@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/hyperid/-/hyperid-1.4.1.tgz#5df41b25a86171661b37534c3404655ae7657d29" + integrity sha512-rtuRoJyEPZZEGN25T1PC2/IYxdApJEYbBB+8e7vZMFnfhZV7HobkpUr7z1gZvcxqleg4OVg0YrMEtfmIoV85gQ== + dependencies: + uuid "^3.2.1" + uuid-parse "^1.0.0" + iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -7666,7 +7674,12 @@ util.promisify@^1.0.0: define-properties "^1.1.2" object.getownpropertydescriptors "^2.0.3" -uuid@^3.3.2: +uuid-parse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/uuid-parse/-/uuid-parse-1.0.0.tgz#f4657717624b0e4b88af36f98d89589a5bbee569" + integrity sha1-9GV3F2JLDkuIrzb5jYlYmlu+5Wk= + +uuid@^3.2.1, uuid@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==