|
1 | | -#!/usr/bin/env bash |
2 | | -# Use this script to test if a given TCP host/port are available |
| 1 | +#!/bin/sh |
| 2 | +# shellcheck shell=dash |
3 | 3 |
|
4 | | -cmdname=$(basename $0) |
| 4 | +# |
| 5 | +# Waits for the given host(s) to be available via TCP before executing a given |
| 6 | +# command. |
| 7 | +# |
| 8 | +# Usage: ./wait.sh [-t timeout] host:port [host:port] [...] [-- command args...] |
| 9 | +# |
| 10 | +# It accepts a number of `host:port` combinations to connect to via netcat. |
| 11 | +# The command to execute after each host is reachable can be supplied after the |
| 12 | +# `--` argument. |
| 13 | +# The default timeout of 10 seconds can be changed via `-t timeout` argument. |
| 14 | +# |
| 15 | +# Copyright 2016, Sebastian Tschan |
| 16 | +# https://blueimp.net |
| 17 | +# |
| 18 | +# Licensed under the MIT license: |
| 19 | +# https://opensource.org/licenses/MIT |
| 20 | +# |
5 | 21 |
|
6 | | -echoerr() { if [[ $QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } |
| 22 | +set -e |
7 | 23 |
|
8 | | -usage() |
9 | | -{ |
10 | | - cat << USAGE >&2 |
11 | | -Usage: |
12 | | - $cmdname host:port [-s] [-t timeout] [-- command args] |
13 | | - -h HOST | --host=HOST Host or IP under test |
14 | | - -p PORT | --port=PORT TCP port under test |
15 | | - Alternatively, you specify the host and port as host:port |
16 | | - -s | --strict Only execute subcommand if the test succeeds |
17 | | - -q | --quiet Don't output any status messages |
18 | | - -t TIMEOUT | --timeout=TIMEOUT |
19 | | - Timeout in seconds, zero for no timeout |
20 | | - -- COMMAND ARGS Execute command with args after the test finishes |
21 | | -USAGE |
22 | | - exit 1 |
23 | | -} |
| 24 | +TIMEOUT=10 |
24 | 25 |
|
25 | | -wait_for() |
26 | | -{ |
27 | | - if [[ $TIMEOUT -gt 0 ]]; then |
28 | | - echoerr "$cmdname: waiting $TIMEOUT seconds for $HOST:$PORT" |
29 | | - else |
30 | | - echoerr "$cmdname: waiting for $HOST:$PORT without a timeout" |
31 | | - fi |
32 | | - start_ts=$(date +%s) |
33 | | - while : |
34 | | - do |
35 | | - if [[ $ISBUSY -eq 1 ]]; then |
36 | | - nc -z $HOST $PORT |
37 | | - result=$? |
38 | | - else |
39 | | - (echo > /dev/tcp/$HOST/$PORT) >/dev/null 2>&1 |
40 | | - result=$? |
41 | | - fi |
42 | | - if [[ $result -eq 0 ]]; then |
43 | | - end_ts=$(date +%s) |
44 | | - echoerr "$cmdname: $HOST:$PORT is available after $((end_ts - start_ts)) seconds" |
45 | | - break |
46 | | - fi |
47 | | - sleep 1 |
48 | | - done |
49 | | - return $result |
| 26 | +is_integer() { |
| 27 | + test "$1" -eq "$1" 2> /dev/null |
50 | 28 | } |
51 | 29 |
|
52 | | -wait_for_wrapper() |
53 | | -{ |
54 | | - # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 |
55 | | - if [[ $QUIET -eq 1 ]]; then |
56 | | - timeout $BUSYTIMEFLAG $TIMEOUT $0 --quiet --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & |
57 | | - else |
58 | | - timeout $BUSYTIMEFLAG $TIMEOUT $0 --child --host=$HOST --port=$PORT --timeout=$TIMEOUT & |
59 | | - fi |
60 | | - PID=$! |
61 | | - trap "kill -INT -$PID" INT |
62 | | - wait $PID |
63 | | - RESULT=$? |
64 | | - if [[ $RESULT -ne 0 ]]; then |
65 | | - echoerr "$cmdname: timeout occurred after waiting $TIMEOUT seconds for $HOST:$PORT" |
66 | | - fi |
67 | | - return $RESULT |
| 30 | +connect_to_service() { |
| 31 | + nc -w 1 -z "$1" "$2" |
68 | 32 | } |
69 | 33 |
|
70 | | -# process arguments |
71 | | -while [[ $# -gt 0 ]] |
72 | | -do |
73 | | - case "$1" in |
74 | | - *:* ) |
75 | | - hostport=(${1//:/ }) |
76 | | - HOST=${hostport[0]} |
77 | | - PORT=${hostport[1]} |
78 | | - shift 1 |
79 | | - ;; |
80 | | - --child) |
81 | | - CHILD=1 |
82 | | - shift 1 |
83 | | - ;; |
84 | | - -q | --quiet) |
85 | | - QUIET=1 |
86 | | - shift 1 |
87 | | - ;; |
88 | | - -s | --strict) |
89 | | - STRICT=1 |
90 | | - shift 1 |
91 | | - ;; |
92 | | - -h) |
93 | | - HOST="$2" |
94 | | - if [[ $HOST == "" ]]; then break; fi |
95 | | - shift 2 |
96 | | - ;; |
97 | | - --host=*) |
98 | | - HOST="${1#*=}" |
99 | | - shift 1 |
100 | | - ;; |
101 | | - -p) |
102 | | - PORT="$2" |
103 | | - if [[ $PORT == "" ]]; then break; fi |
104 | | - shift 2 |
105 | | - ;; |
106 | | - --port=*) |
107 | | - PORT="${1#*=}" |
108 | | - shift 1 |
109 | | - ;; |
110 | | - -t) |
111 | | - TIMEOUT="$2" |
112 | | - if [[ $TIMEOUT == "" ]]; then break; fi |
113 | | - shift 2 |
114 | | - ;; |
115 | | - --timeout=*) |
116 | | - TIMEOUT="${1#*=}" |
117 | | - shift 1 |
118 | | - ;; |
119 | | - --) |
120 | | - shift |
121 | | - CLI=("$@") |
122 | | - break |
123 | | - ;; |
124 | | - --help) |
125 | | - usage |
126 | | - ;; |
127 | | - *) |
128 | | - echoerr "Unknown argument: $1" |
129 | | - usage |
130 | | - ;; |
131 | | - esac |
132 | | -done |
133 | | - |
134 | | -if [[ "$HOST" == "" || "$PORT" == "" ]]; then |
135 | | - echoerr "Error: you need to provide a host and port to test." |
136 | | - usage |
137 | | -fi |
138 | | - |
139 | | -TIMEOUT=${TIMEOUT:-15} |
140 | | -STRICT=${STRICT:-0} |
141 | | -CHILD=${CHILD:-0} |
142 | | -QUIET=${QUIET:-0} |
143 | | - |
144 | | -# check to see if timeout is from busybox? |
145 | | -# check to see if timeout is from busybox? |
146 | | -TIMEOUT_PATH=$(realpath $(which timeout)) |
147 | | -if [[ $TIMEOUT_PATH =~ "busybox" ]]; then |
148 | | - ISBUSY=1 |
149 | | - BUSYTIMEFLAG="-t" |
150 | | -else |
151 | | - ISBUSY=0 |
152 | | - BUSYTIMEFLAG="" |
153 | | -fi |
154 | | - |
155 | | -if [[ $CHILD -gt 0 ]]; then |
156 | | - wait_for |
157 | | - RESULT=$? |
158 | | - exit $RESULT |
159 | | -else |
160 | | - if [[ $TIMEOUT -gt 0 ]]; then |
161 | | - wait_for_wrapper |
162 | | - RESULT=$? |
163 | | - else |
164 | | - wait_for |
165 | | - RESULT=$? |
| 34 | +wait_for_service() { |
| 35 | + local host="${1%:*}" |
| 36 | + local port="${1#*:}" |
| 37 | + local output |
| 38 | + if ! is_integer "$port"; then |
| 39 | + printf 'Error: "%s" is not a valid host:port combination.\n' "$1" >&2 |
| 40 | + return 1 |
| 41 | + fi |
| 42 | + printf 'Waiting for %s to become available ... ' "$1" >&2 |
| 43 | + # shellcheck disable=SC2155 |
| 44 | + local timeout=$(($(date +%s)+TIMEOUT)) |
| 45 | + while ! output="$(connect_to_service "$host" "$port" 2>&1)"; do |
| 46 | + if [ "$(date +%s)" -gt "$timeout" ]; then |
| 47 | + echo 'timeout' >&2 |
| 48 | + if [ ! -z "$output" ]; then |
| 49 | + echo "$output" >&2 |
| 50 | + fi |
| 51 | + return 1 |
166 | 52 | fi |
167 | | -fi |
| 53 | + sleep 1 |
| 54 | + done |
| 55 | + echo 'done' >&2 |
| 56 | +} |
168 | 57 |
|
169 | | -if [[ $CLI != "" ]]; then |
170 | | - if [[ $RESULT -ne 0 && $STRICT -eq 1 ]]; then |
171 | | - echoerr "$cmdname: strict mode, refusing to execute subprocess" |
172 | | - exit $RESULT |
| 58 | +while [ $# != 0 ]; do |
| 59 | + if [ "$1" = '-t' ] || [ "$1" = '--timeout' ]; then |
| 60 | + if ! is_integer "$2"; then |
| 61 | + printf 'Error: "%s" is not a timeout integer.\n' "$2" >&2 |
| 62 | + exit 1 |
173 | 63 | fi |
174 | | - exec "${CLI[@]}" |
175 | | -else |
176 | | - exit $RESULT |
177 | | -fi |
| 64 | + TIMEOUT="$2" |
| 65 | + shift 2 |
| 66 | + fi |
| 67 | + if [ "$1" = '--' ]; then |
| 68 | + shift |
| 69 | + exec "$@" |
| 70 | + fi |
| 71 | + wait_for_service "$1" |
| 72 | + shift |
| 73 | +done |
0 commit comments