Skip to content
Snippets Groups Projects
Commit c310a110 authored by nimrod's avatar nimrod
Browse files

Add a Compose health check script.

This script does a better at checking the health of the Docker Compose
project.
parent 3ea988c8
No related branches found
No related tags found
No related merge requests found
Pipeline #2396 passed
......@@ -80,3 +80,48 @@ repos:
args:
- "--server"
- https://git.shore.co.il
- repo: https://github.com/ambv/black.git
rev: 21.9b0
hooks:
- id: black
args:
- |
--line-length=79
- repo: https://github.com/PyCQA/prospector.git
rev: 1.5.1
hooks:
- id: prospector
args:
- |-
--max-line-length=79
- |-
--with-tool=pyroma
- |-
--with-tool=bandit
- |-
--without-tool=pep257
- |-
--doc-warnings
- |-
--test-warnings
- |-
--full-pep8
- |-
--strictness=high
- |-
--no-autodetect
additional_dependencies:
- bandit
- pyroma
- repo: https://gitlab.com/pycqa/flake8.git
rev: 3.9.2
hooks:
- id: flake8
args:
- |-
--doctests
additional_dependencies:
- flake8-bugbear
FROM docker:20.10
# hadolint ignore=DL3018
RUN apk add --update-cache --no-cache docker-compose
RUN apk add --update-cache --no-cache \
docker-compose \
docker-py \
py3-dotenv \
&& \
rm -rf /root/.cache /tmp/* /var/tmp/*
#!/usr/bin/env python3
# pylint: disable=invalid-name
"""Checks the health of a Docker Compose deployment."""
import argparse
import datetime
import os
import sys
import time
import docker # pylint: disable=import-error
import dotenv # pylint: disable=import-error
def get_project_containers_health(client, project):
"""Get a Docker connection and the Docker Compose project name and return a
dictionary of container objects and their health status (if available, else
their status)."""
return {
i: i.attrs["State"]["Health"]["Status"]
if "Health" in i.attrs["State"]
else i.attrs["State"]["Status"]
for i in client.containers.list(
filters={"label": f"com.docker.compose.project={project}"}
)
}
def check_project_health(project, timeout):
"""Checks if a Docker Compose project is healthy.
If all of the containers are healthy or running (because there's no
healthcheck set), return True. If any of the containers is starting (health
check hasn't run yet or in the grace period), sleep 10 and try again
(ignoring the timeout). Otherwise, if haven't passed the timeout, sleep 10
and try again, if over the timeout, return if all the containers are
healthy or running.
"""
starttime = datetime.datetime.now()
deadline = starttime + datetime.timedelta(seconds=timeout)
docker_client = docker.from_env()
docker_client.ping()
while True:
healths = get_project_containers_health(docker_client, project)
# pylint: disable=no-else-return
if all(map(lambda x: x in ["healthy", "running"], healths.values())):
return True
elif any(map(lambda x: x == "starting", healths.values())):
time.sleep(10)
continue
elif datetime.datetime.now() > deadline:
break
else:
time.sleep(10)
if all(map(lambda x: x in ["healthy", "running"], healths.values())):
return True
unhealthy = ", ".join(
[
f"{x.id}({x.name})"
for x in healths
if healths[x] not in ["healthy", "running"]
]
)
print(f"""Containers {unhealthy} are not healthy.""")
return False
def main():
"""Main entrypoint."""
epilog = (
"The Docker Compose project name is resolved in the following order:" # noqa: E501
"\n1. From the command line parameter."
"\n2. From the COMPOSE_PROJECT_NAME variable in the .env file."
"\n3. From the COMPOSE_PROJECT_NAME environment variable."
)
arg_parser = argparse.ArgumentParser(
description=__doc__,
epilog=epilog,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
arg_parser.add_argument(
"project", help="Name of the Compose Project.", nargs="?"
)
arg_parser.add_argument(
"-t",
"--timeout",
help="Number of seconds to wait for the deployment to be healthy, defaults to 300.", # noqa: E501
type=int,
default=300,
)
args = arg_parser.parse_args()
if args.project:
project = args.project
else:
env = dotenv.dotenv_values(dotenv_path=".env")
if "COMPOSE_PROJECT_NAME" in env:
project = env["COMPOSE_PROJECT_NAME"]
elif "COMPOSE_PROJECT_NAME" in os.environ:
project = os.environ["COMPOSE_PROJECT_NAME"]
else:
arg_parser.error(
"Compose project wasn't specified, the COMPOSE_PROJECT_NAME variable is missing from the environment and from the .env file." # noqa: E501
)
if check_project_health(project, args.timeout):
return 0
return 1
if __name__ == "__main__":
sys.exit(main())
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment