Skip to content

Commit

Permalink
feat: add the 'wait-for-dns' flag (#47)
Browse files Browse the repository at this point in the history
Closes #41.
  • Loading branch information
dwmkerr committed Oct 3, 2019
1 parent 68b95b7 commit 31418c2
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 8 deletions.
12 changes: 10 additions & 2 deletions README.md
@@ -1,6 +1,12 @@
# wait-port [![CircleCI](https://circleci.com/gh/dwmkerr/wait-port.svg?style=shield)](https://circleci.com/gh/dwmkerr/wait-port) [![codecov](https://codecov.io/gh/dwmkerr/wait-port/branch/master/graph/badge.svg)](https://codecov.io/gh/dwmkerr/wait-port) [![npm version](https://badge.fury.io/js/wait-port.svg)](https://badge.fury.io/js/wait-port) [![Greenkeeper badge](https://badges.greenkeeper.io/dwmkerr/wait-port.svg)](https://greenkeeper.io/) [![GuardRails badge](https://badges.production.guardrails.io/dwmkerr/wait-port.svg)](https://www.guardrails.io)
# wait-port

Simple binary to wait for a port to open. Useful when writing scripts which need to wait for a server to be availble, creating `docker-compose` commands which wait for servers to start and general server-side shenanigans. Can also wait for an HTTP endpoint to successfully respond.
[![CircleCI](https://circleci.com/gh/dwmkerr/wait-port.svg?style=shield)](https://circleci.com/gh/dwmkerr/wait-port) [![codecov](https://codecov.io/gh/dwmkerr/wait-port/branch/master/graph/badge.svg)](https://codecov.io/gh/dwmkerr/wait-port) [![npm version](https://badge.fury.io/js/wait-port.svg)](https://badge.fury.io/js/wait-port) [![Greenkeeper badge](https://badges.greenkeeper.io/dwmkerr/wait-port.svg)](https://greenkeeper.io/) [![GuardRails badge](https://badges.production.guardrails.io/dwmkerr/wait-port.svg)](https://www.guardrails.io)

Simple binary to wait for a port to open. Useful when writing scripts which need to wait for a server to be available.
- Creating `docker-compose` commands which wait for servers to start
- Wait for an HTTP endpoint to successfully respond
- Wait for DNS records to be resolvable
- Wait for application servers to start

<img src="https://github.com/dwmkerr/wait-port/raw/master/docs/wait-port.gif" alt="wait-port screenshot" width="520px" />

Expand Down Expand Up @@ -67,6 +73,7 @@ The following parameters are accepted:
| `<target>` | Required. The target to test for. Can be just a port, a colon and port (as one would use with [httpie](https://httpie.org/) or host and port. Examples: `8080`, `:3000`, `127.0.0.1:443`. |
| `--output, -o` | Optional. Output style to use. Can be `dots` (default) or `silent` (no output). |
| `--timeout, -t` | Optional. Timeout (in milliseconds). |
| `--wait-for-dns` | Optional. Do not error if the response is `ENOTFOUND`, just keep on waiting (useful if you are waiting for a DNS record to also be created). |

### Error Codes

Expand Down Expand Up @@ -109,6 +116,7 @@ The CLI is a very shallow wrapper around this function. The `params` object take
| `<target>` | `port` | Required. Port to wait for. |
| `--output` | `output` | Optional. Defaults to `dots`. Output style to use. `silent` also accepted. |
| `--timeout, -t` | `timeout` | Optional. Defaults to `0`. Timeout (in milliseconds). If `0`, then the operation will never timeout. |
| `--wait-for-dns` | `waitForDns` | Optional. Defaults to `false`. |

# Developer Guide

Expand Down
7 changes: 6 additions & 1 deletion bin/wait-port.js
Expand Up @@ -13,23 +13,27 @@ program
.description('Wait for a target to accept connections, e.g: wait-port localhost:8080')
.option('-t, --timeout [n]', 'Timeout', parseInt)
.option('-o, --output [mode]', 'Output mode (silent, dots). Default is silent.')
.option('--wait-for-dns', 'Do not fail on ENOTFOUND, meaning you can wait for DNS record creation. Default is false.')
.arguments('<target>')
.action((target) => {
// Validate the parameters (extractTarget) will throw if target is invalid).
const { protocol, host, port, path } = extractTarget(target);
const timeout = program.timeout || 0;
const output = program.output;
const waitForDns = program.waitForDns;

debug(`Timeout: ${timeout}`);
debug(`Target: ${target} => ${protocol}://${host}:${port}${path}`);
debug(`waitForDns: ${waitForDns}`);

const params = {
timeout,
protocol,
host,
port,
path,
output
output,
waitForDns,
};

waitPort(params)
Expand Down Expand Up @@ -59,6 +63,7 @@ program.on('--help', () => {
console.log(' $ wait-port -t 10 :8080');
console.log(' $ wait-port google.com:443');
console.log(' $ wait-port http://localhost:5000/healthcheck');
console.log(' $ wait-port --wait-for-dns http://mynewdomain.com:80');
console.log('');
});

Expand Down
7 changes: 6 additions & 1 deletion lib/validate-parameters.js
Expand Up @@ -32,6 +32,10 @@ function validateParameters(params) {
if (!Number.isInteger(timeout)) throw new ValidationError('\'timeout\' must be a number.');
if (timeout < 0) throw new ValidationError('\'timeout\' must be greater or equal to 0.');

// Validate and coerce the wait-for-dns parameter.
const waitForDns = params.waitForDns || false;
if (typeof waitForDns !== 'boolean') throw new ValidationError('\'wait-for-dns\' must be a boolean.');

// Coerce the output.
const output = params.output || 'dots';

Expand All @@ -50,7 +54,8 @@ function validateParameters(params) {
path,
interval,
timeout,
output
output,
waitForDns,
};
}

Expand Down
20 changes: 19 additions & 1 deletion lib/validate-parameters.spec.js
Expand Up @@ -8,7 +8,8 @@ describe('validateParameters', () => {
port: 8080,
interval: 2000,
timeout: 0,
output: 'silent'
output: 'silent',
waitForDns: false,
};
};

Expand Down Expand Up @@ -126,4 +127,21 @@ describe('validateParameters', () => {

});

it('should default the wait-for-dns flag to false', () => {

const params = validParams();
delete params.waitForDns;
const validatedParams = validateParameters(params);
assert.equal(validatedParams.waitForDns, false);

});

it('should throw if the wait-for-dns parameter is not a boolean', () => {

const params = validParams();
params.waitForDns = 'not-a-boolean';
assert.throws(() => validateParameters(params), /wait-for-dns.*boolean.*/);

});

});
12 changes: 10 additions & 2 deletions lib/wait-port.js
Expand Up @@ -110,6 +110,13 @@ function tryConnect(options, timeout) {
// lookup fail (normally a problem if the domain is wrong).
debug('Socket cannot be opened: ENOTFOUND');
socket.destroy();

// If we are going to wait for DNS records, we can actually just try
// again...
if (options.waitForDns === true) return resolve(false);

// ...otherwise, we will explicitly fail with a meaningful error for
// the user.
return reject(new ConnectionError(`The address '${options.host}' cannot be found`));
}

Expand Down Expand Up @@ -171,7 +178,8 @@ function waitPort(params) {
path,
interval,
timeout,
output
output,
waitForDns,
} = validateParameters(params);

// Keep track of the start time (needed for timeout calcs).
Expand All @@ -187,7 +195,7 @@ function waitPort(params) {
// Start trying to connect.
const loop = () => {
outputFunction.tryConnect();
tryConnect({ protocol, host, port, path }, connectTimeout)
tryConnect({ protocol, host, port, path, waitForDns }, connectTimeout)
.then((open) => {
debug(`Socket status is: ${open}`);

Expand Down
20 changes: 19 additions & 1 deletion lib/wait-port.spec.js
Expand Up @@ -81,8 +81,26 @@ describe('wait-port', () => {
assert(/.*address.*ireallyhopethatthisdomainnamedoesnotexist.com/.test(err.message));
});
});


it('should not error if the address is not found when the \'wait-for-dns\' flag is used', () => {

const timeout = 1000;
const delta = 500;

// Start waiting for port 9021 to open.
const start = new Date();
return waitPort({ host: 'ireallyhopethatthisdomainnamedoesnotexist.com', waitForDns: true, port: 9021, timeout, output: 'silent' })
.then((open) => {
assert(open === false, 'The port should not be open.');

// Make sure we are close to the timeout.
const elapsed = new Date() - start;
assert(((timeout - delta) < elapsed) && (elapsed < (timeout + delta)),
`Timeout took ${elapsed}ms, should be close to ${timeout}ms.`);
});
});


it('should successfully wait for a valid http response', () => {
const server = http.createServer((req, res) => {
res.writeHead(200);
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -14,6 +14,7 @@
"lint": "eslint .",
"test": "DEBUG=wait-port nyc --report-dir 'artifacts/coverage' -x 'lib/**/*.spec.js' --reporter=html --reporter=text mocha --recursive -t 10000 'lib/**/*.spec.js'",
"test:ci": "DEBUG=wait-port nyc --report-dir 'artifacts/coverage' -x 'lib/**/*.spec.js' --reporter=html --reporter=text mocha --recursive -t 10000 'lib/**/*.spec.js' --reporter mocha-junit-reporter --reporter-options mochaFile=./artifacts/test-reports/test-results.xml",
"test:debug": "DEBUG=wait-port mocha --inspect --inspect-brk -t 10000 'lib/**/*.spec.js'",
"test:watch": "DEBUG=wait-port mocha --watch --recursive -t 10000 'lib/**/*.spec.js'",
"report-coverage": "nyc report --reporter=text-lcov > ./artifacts/coverage/coverage.lcov && codecov",
"debug": "DEBUG=wait-port mocha --recursive --inspect --debug-brk 'lib/**/*.spec.js'",
Expand Down

0 comments on commit 31418c2

Please sign in to comment.