Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for alpine3.10 #4

Merged
merged 16 commits into from
Dec 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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