Skip to content

Commit

Permalink
Merge pull request #4 from alallier/fixForAlpine3-10
Browse files Browse the repository at this point in the history
Fix for alpine3.10
  • Loading branch information
alallier committed Dec 18, 2020
2 parents e914e5a + 913b6b3 commit 486780c
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
**/*.pyc
.pydevproject

/vendor/
32 changes: 21 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
## wait-for-it
# wait-for-it

`wait-for-it.sh` is a pure bash script that will wait on the availability of a host and TCP port. It is useful for synchronizing the spin-up of interdependent services, such as linked docker containers. Since it is a pure bash script, it does not have any external dependencies.
`wait-for-it.sh` is a pure bash script that will wait on the availability of a
host and TCP port. It is useful for synchronizing the spin-up of
interdependent services, such as linked docker containers. Since it is a pure
bash script, it does not have any external dependencies.

## Usage

```
```text
wait-for-it.sh host:port [-s] [-t timeout] [-- command args]
-h HOST | --host=HOST Host or IP under test
-p PORT | --port=PORT TCP port under test
Expand All @@ -18,36 +21,43 @@ wait-for-it.sh host:port [-s] [-t timeout] [-- command args]

## Examples

For example, let's test to see if we can access port 80 on www.google.com, and if it is available, echo the message `google is up`.
For example, let's test to see if we can access port 80 on `www.google.com`,
and if it is available, echo the message `google is up`.

```
```text
$ ./wait-for-it.sh www.google.com:80 -- echo "google is up"
wait-for-it.sh: waiting 15 seconds for www.google.com:80
wait-for-it.sh: www.google.com:80 is available after 0 seconds
google is up
```

You can set your own timeout with the `-t` or `--timeout=` option. Setting the timeout value to 0 will disable the timeout:
You can set your own timeout with the `-t` or `--timeout=` option. Setting
the timeout value to 0 will disable the timeout:

```
```text
$ ./wait-for-it.sh -t 0 www.google.com:80 -- echo "google is up"
wait-for-it.sh: waiting for www.google.com:80 without a timeout
wait-for-it.sh: www.google.com:80 is available after 0 seconds
google 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:
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:

```
```text
$ ./wait-for-it.sh www.google.com:81 --timeout=1 --strict -- echo "google is up"
wait-for-it.sh: waiting 1 seconds for www.google.com:81
wait-for-it.sh: timeout occurred after waiting 1 seconds for www.google.com:81
wait-for-it.sh: strict mode, refusing to execute subprocess
```

If you don't want to execute a subcommand, leave off the `--` argument. This way, you can test the exit condition of `wait-for-it.sh` in your own scripts, and determine how to proceed:
If you don't want to execute a subcommand, leave off the `--` argument. This
way, you can test the exit condition of `wait-for-it.sh` in your own scripts,
and determine how to proceed:

```
```text
$ ./wait-for-it.sh www.google.com:80
wait-for-it.sh: waiting 15 seconds for www.google.com:80
wait-for-it.sh: www.google.com:80 is available after 0 seconds
Expand Down
7 changes: 7 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "vishnubob/wait-for-it",
"description": "Pure bash script to test and wait on the availability of a TCP host and port",
"type": "library",
"license": "MIT",
"bin": ["wait-for-it.sh"]
}
18 changes: 18 additions & 0 deletions test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Tests for wait-for-it

* wait-for-it.py - pytests for wait-for-it.sh
* container-runners.py - Runs wait-for-it.py tests in multiple containers
* requirements.txt - pip requirements for container-runners.py

To run the basic tests:

```
python wait-for-it.py
```

Many of the issues encountered have been related to differences between operating system versions. The container-runners.py script provides an easy way to run the python wait-for-it.py tests against multiple system configurations:

```
pip install -r requirements.txt
python container-runners.py
```
35 changes: 35 additions & 0 deletions test/container-runners.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env python

# Unit tests to run wait-for-it.py unit tests in several different docker images

import unittest
import os
import docker
from parameterized import parameterized

client = docker.from_env()
app_path = os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..'))
volumes = {app_path: {'bind': '/app', 'mode': 'ro'}}

class TestContainers(unittest.TestCase):
"""
Test multiple container types with the test cases in wait-for-it.py
"""

@parameterized.expand([
"python:3.5-buster",
"python:3.5-stretch",
"dougg/alpine-busybox:alpine-3.11.3_busybox-1.30.1",
"dougg/alpine-busybox:alpine-3.11.3_busybox-1.31.1"
])
def test_image(self, image):
print(image)
command="/app/test/wait-for-it.py"
container = client.containers.run(image, command=command, volumes=volumes, detach=True)
result = container.wait()
logs = container.logs()
container.remove()
self.assertEqual(result["StatusCode"], 0)

if __name__ == '__main__':
unittest.main()
2 changes: 2 additions & 0 deletions test/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
docker>=4.0.0
parameterized>=0.7.0
59 changes: 31 additions & 28 deletions test/wait-for-it.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/usr/bin/env python

import unittest
import shlex
from subprocess import Popen, PIPE
Expand All @@ -24,17 +26,17 @@ def execute(self, cmd):
proc = Popen(args, stdout=PIPE, stderr=PIPE)
out, err = proc.communicate()
exitcode = proc.returncode
return exitcode, out, err
return exitcode, out.decode('utf-8'), err.decode('utf-8')

def open_local_port(self, host="localhost", port=8929, timeout=5):
def open_local_port(self, timeout=5):
s = socket.socket()
s.bind((host, port))
s.bind(('', 0))
s.listen(timeout)
return s
return s, s.getsockname()[1]

def check_args(self, args, stdout_regex, stderr_regex, exitcode):
def check_args(self, args, stdout_regex, stderr_regex, should_succeed):
command = self.wait_script + " " + args
actual_exitcode, out, err = self.execute(command)
exitcode, out, err = self.execute(command)

# Check stderr
msg = ("Failed check that STDERR:\n" +
Expand All @@ -51,7 +53,7 @@ def check_args(self, args, stdout_regex, stderr_regex, exitcode):
self.assertIsNotNone(re.match(stdout_regex, out, re.DOTALL), msg)

# Check exit code
self.assertEqual(actual_exitcode, exitcode)
self.assertEqual(should_succeed, exitcode == 0)

def setUp(self):
script_path = os.path.dirname(sys.argv[0])
Expand All @@ -67,7 +69,7 @@ def test_no_args(self):
"",
"^$",
MISSING_ARGS_TEXT,
1
False
)
# Return code should be 1 when called with no args
exitcode, out, err = self.execute(self.wait_script)
Expand All @@ -79,7 +81,7 @@ def test_help(self):
"--help",
"",
HELP_TEXT,
1
False
)

def test_no_port(self):
Expand All @@ -88,7 +90,7 @@ def test_no_port(self):
"--host=localhost",
"",
MISSING_ARGS_TEXT,
1
False
)

def test_no_host(self):
Expand All @@ -97,17 +99,17 @@ def test_no_host(self):
"--port=80",
"",
MISSING_ARGS_TEXT,
1
False
)

def test_host_port(self):
""" Check that --host and --port args work correctly """
soc = self.open_local_port(port=8929)
soc, port = self.open_local_port()
self.check_args(
"--host=localhost --port=8929 --timeout=1",
"--host=localhost --port={0} --timeout=1".format(port),
"",
"wait-for-it.sh: waiting 1 seconds for localhost:8929",
0
"wait-for-it.sh: waiting 1 seconds for localhost:{0}".format(port),
True
)
soc.close()

Expand All @@ -116,15 +118,16 @@ def test_combined_host_port(self):
Tests that wait-for-it.sh returns correctly after establishing a
connectionm using combined host and ports
"""
soc = self.open_local_port(port=8929)
soc, port = self.open_local_port()
self.check_args(
"localhost:8929 --timeout=1",
"localhost:{0} --timeout=1".format(port),
"",
"wait-for-it.sh: waiting 1 seconds for localhost:8929",
0
"wait-for-it.sh: waiting 1 seconds for localhost:{0}".format(port),
True
)
soc.close()


def test_port_failure_with_timeout(self):
"""
Note exit status of 124 is exected, passed from the timeout command
Expand All @@ -133,19 +136,19 @@ def test_port_failure_with_timeout(self):
"localhost:8929 --timeout=1",
"",
".*timeout occurred after waiting 1 seconds for localhost:8929",
124
False
)

def test_command_execution(self):
"""
Checks that a command executes correctly after a port test passes
"""
soc = self.open_local_port(port=8929)
soc, port = self.open_local_port()
self.check_args(
"localhost:8929 -- echo \"CMD OUTPUT\"",
"localhost:{0} -- echo \"CMD OUTPUT\"".format(port),
"CMD OUTPUT",
".*wait-for-it.sh: localhost:8929 is available after 0 seconds",
0
".*wait-for-it.sh: localhost:{0} is available after 0 seconds".format(port),
True
)
soc.close()

Expand All @@ -154,12 +157,12 @@ def test_failed_command_execution(self):
Check command failure. The command in question outputs STDERR and
an exit code of 2
"""
soc = self.open_local_port(port=8929)
soc, port = self.open_local_port()
self.check_args(
"localhost:8929 -- ls not_real_file",
"localhost:{0} -- ls not_real_file".format(port),
"",
".*No such file or directory\n",
2
False
)
soc.close()

Expand All @@ -172,7 +175,7 @@ def test_command_after_connection_failure(self):
"localhost:8929 --timeout=1 -- echo \"CMD OUTPUT\"",
"CMD OUTPUT",
".*timeout occurred after waiting 1 seconds for localhost:8929",
0
True
)

if __name__ == '__main__':
Expand Down
20 changes: 13 additions & 7 deletions wait-for-it.sh
Original file line number Diff line number Diff line change
Expand Up @@ -140,17 +140,23 @@ WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15}
WAITFORIT_STRICT=${WAITFORIT_STRICT:-0}
WAITFORIT_CHILD=${WAITFORIT_CHILD:-0}
WAITFORIT_QUIET=${WAITFORIT_QUIET:-0}

# check to see if timeout is from busybox?
WAITFORIT_ISBUSY=0
WAITFORIT_BUSYTIMEFLAG=""
WAITFORIT_TIMEOUT_PATH=$(type -p timeout)
WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH)

# check to see if we're using busybox?
if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then
WAITFORIT_ISBUSY=1
WAITFORIT_BUSYTIMEFLAG="-t"
WAITFORIT_ISBUSY=1
fi

else
WAITFORIT_ISBUSY=0
WAITFORIT_BUSYTIMEFLAG=""
# see if timeout.c args have been updated in busybox v1.30.0 or newer
# note: this requires the use of bash on Alpine
if [[ $WAITFORIT_ISBUSY -eq 1 && $(busybox | head -1) =~ ^.*v([[:digit:]]+)\.([[:digit:]]+)\..+$ ]]; then
if [[ ${BASH_REMATCH[1]} -le 1 && ${BASH_REMATCH[2]} -lt 30 ]]; then
# using pre 1.30.0 version with `-t SEC` arg
WAITFORIT_BUSYTIMEFLAG="-t"
fi
fi

if [[ $WAITFORIT_CHILD -gt 0 ]]; then
Expand Down

0 comments on commit 486780c

Please sign in to comment.