diff --git a/.travis.yml b/.travis.yml index 90bc111..e8a4731 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,5 +6,9 @@ services: - docker before_install: - - docker build -t mrako/wait-for . - - docker run mrako/wait-for + - docker build -t eficode/wait-for . + +script: + - npm install + - ./run_tests.sh + - docker run eficode/wait-for diff --git a/Dockerfile b/Dockerfile index 412d8e1..f636844 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,4 +8,5 @@ WORKDIR /app COPY . /app RUN npm install -CMD ./node_modules/.bin/bats wait-for.bats +# On launch, run the test suite via npm +CMD npm test diff --git a/README.md b/README.md index b6c83e4..ca2d3a9 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,14 @@ When using this tool, you only need to pick the `wait-for` file as part of your project. -[![Build Status](https://travis-ci.org/mrako/wait-for.svg?branch=master)](https://travis-ci.org/mrako/wait-for) +[![Build Status](https://travis-ci.org/eficode/wait-for.svg?branch=master)](https://travis-ci.org/eficode/wait-for) ## Usage ``` -./wait-for host:port [-t timeout] [-- command args] +wait-for host:port [-t timeout] [-- command args] -q | --quiet Do not output any status messages + -l | --loose Execute subcommand even if the test times out -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout -- COMMAND ARGS Execute command with args after the test finishes ``` @@ -21,11 +22,19 @@ To check if [eficode.com](https://eficode.com) is available: ``` $ ./wait-for www.eficode.com:80 -- echo "Eficode site is up" - -Connection to www.eficode.com port 80 [tcp/http] succeeded! Eficode site is up ``` +The subcommand will be executed regardless if the service is up or not. If you wish to execute the subcommand only if the service is up, add the --strict argument. In this example, we will test port 81 on www.google.com which will fail: + +``` +$ ./wait-for www.google.com:81 --timeout=1 -- echo "google is up" +Operation timed out +$ ./wait-for www.google.com:81 --timeout=1 --loose -- echo "waited for google" +Operation timed out +waited for google +``` + To wait for database container to become available: @@ -48,4 +57,4 @@ services: Ironically testing is done using [bats](https://github.com/sstephenson/bats), which on the other hand is depending on [bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell)). docker build -t wait-for . - docker run -t wait-for \ No newline at end of file + docker run -t wait-for diff --git a/package.json b/package.json index 6d167cc..7aea0e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,8 @@ { + "name": "wait-for", + "version": "0.1.0", "scripts": { - "test": "./node_modules/.bin/bats wait-for.bats" + "test": "./run_tests.sh" }, "dependencies": { "bats": "^0.4.2" diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 0000000..13bf63a --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# Although it would be possible to just call this directly from the Dockerfile, +# centralizing tests in this file allows both the docker container and the +# CI machine to run the same set of tests for an additional datapoint -- +# which gives a better chance of turning up POSIX noncompliance + +./node_modules/.bin/bats wait-for.bats \ No newline at end of file diff --git a/wait-for b/wait-for index 077a8d3..cc168b0 100755 --- a/wait-for +++ b/wait-for @@ -1,40 +1,70 @@ -#!/bin/sh +#!/usr/bin/env bash + +OLD_TIMEOUT=$TIMEOUT +OLD_QUIET=$QUIET +OLD_PORT=$PORT +OLD_HOST=$HOST +OLD_LOOSE=$LOOSE TIMEOUT=15 QUIET=0 +LOOSE=0 + +if ! which nc >/dev/null; then + echo "Netcat is not installed. This script requires netcat to work correctly." + exit 1 +fi echoerr() { if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi } +conditionally_output() { + if [ "$QUIET" -ne 1 ]; then + "$@" + else + "$@" > /dev/null 2>&1 + fi +} + usage() { exitcode="$1" cat << USAGE >&2 Usage: - $cmdname host:port [-t timeout] [-- command args] + $(basename $0) host:port [-t timeout] [-- command args] -q | --quiet Do not output any status messages + -l | --loose Execute subcommand even if the test times out -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout -- COMMAND ARGS Execute command with args after the test finishes USAGE exit "$exitcode" } +test_connection() { + conditionally_output echo "Testing connection to $1:$2..." + + # force a 1-second timeout on darwin (https://stackoverflow.com/a/20460402/2063546) + if [ -z "${OSTYPE##*darwin*}" ] ; then + conditionally_output nc -z -w 1 -G 1 "$1" "$2" + else + conditionally_output nc -z -w 1 "$1" "$2" > /dev/null 2>&1 + fi +} + wait_for() { - command="$*" + local result for i in `seq $TIMEOUT` ; do - nc -z "$HOST" "$PORT" > /dev/null 2>&1 - + # use a 1-second timeout, but still sleep 0.1 seconds after just to be safe + test_connection "$HOST" "$PORT" result=$? - if [ $result -eq 0 ] ; then - if [ -n "$command" ] ; then - exec $command - fi - exit 0 - fi + if [ $result -eq 0 ] ; then break ; fi sleep 1 done - echo "Operation timed out" >&2 - exit 1 + [ $result -ne 0 ] && echoerr "Operation timed out" + if [ $result -eq 0 -o $LOOSE -eq 1 -a $# -gt 0 ] ; then + TIMEOUT=$OLD_TIMEOUT QUIET=$OLD_QUIET PORT=$OLD_PORT HOST=$OLD_HOST LOOSE=$OLD_LOOSE exec "$@" + fi + exit $result } while [ $# -gt 0 ] @@ -49,6 +79,10 @@ do QUIET=1 shift 1 ;; + -l | --loose) + LOOSE=1 + shift 1 + ;; -t) TIMEOUT="$2" if [ "$TIMEOUT" = "" ]; then break; fi diff --git a/wait-for.bats b/wait-for.bats index cbea6a4..fa873ac 100644 --- a/wait-for.bats +++ b/wait-for.bats @@ -2,7 +2,7 @@ @test "google should be immediately found" { run ./wait-for google.com:80 -- echo 'success' - + [ "$output" = "success" ] } @@ -12,3 +12,28 @@ [ "$status" -ne 0 ] [ "$output" != "success" ] } + +@test "nonexistent server should start command if loose option is specified" { + run ./wait-for -t 1 -l noserver:9999 -- echo 'passable' 2>&1 + + [ "$status" -eq 0 ] + + [ "${lines[0]}" = "Operation timed out" ] + [ "${lines[1]}" = "passable" ] +} + +@test "preserve existing environment variables" { + TIMEOUT=mytimeout + QUIET=myquiet + HOST=myhost + PORT=myport + LOOSE=myloose + + run ./wait-for google.com:80 -- echo 'success' + + [ "$(echo $TIMEOUT)" = 'mytimeout' ] + [ "$(echo $QUIET)" = 'myquiet' ] + [ "$(echo $HOST)" = 'myhost' ] + [ "$(echo $PORT)" = 'myport' ] + [ "$(echo $LOOSE)" = 'myloose' ] +}