diff --git a/images/docker/compose-health-check b/images/docker/compose-health-check index f849664791e5c5adc5981834274de8fd2e660ef2..00698f538aa4a96f9245d6be6818f7370bae23d8 100755 --- a/images/docker/compose-health-check +++ b/images/docker/compose-health-check @@ -26,15 +26,22 @@ def get_project_containers_health(client, project): } -def check_project_health(project, timeout): - """Checks if a Docker Compose project is healthy. +def get_unhealthy_project_containers(client, project): + """Returns a list (could be empty) of containers that are not healthy or + not running (in case a health check is not set).""" + healths = get_project_containers_health(client, project) + return [x for x in healths if healths[x] not in ["healthy", "running"]] + + +def wait_for_project_health(project, timeout): + """Wait for a Docker Compose project to be 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. + healthcheck set), return an empty list. If any of the containers are + starting (health check hasn't run yet or in the grace period), sleep 10 and + try again (ignoring the timeout). Otherwise, if the timeout hasn't passed, + sleep 10 and try again, if over the timeout, return a list of the + containers that aren't healthy or aren't running. """ starttime = datetime.datetime.now() deadline = starttime + datetime.timedelta(seconds=timeout) @@ -43,8 +50,8 @@ def check_project_health(project, timeout): 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 + if not get_unhealthy_project_containers(docker_client, project): + return [] elif any(map(lambda x: x == "starting", healths.values())): time.sleep(10) continue @@ -53,24 +60,26 @@ def check_project_health(project, timeout): 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 + return get_project_containers_health(docker_client, project) + + +def get_project_name(args): + """Returns the name of the Docker Compose project, or None if unable to.""" + if args.project: + return args.project + env = dotenv.dotenv_values(dotenv_path=".env") + if "COMPOSE_PROJECT_NAME" in env: + return env["COMPOSE_PROJECT_NAME"] + if "COMPOSE_PROJECT_NAME" in os.environ: + return os.environ["COMPOSE_PROJECT_NAME"] + return None def main(): """Main entrypoint.""" epilog = ( - "The Docker Compose project name is resolved in the following order:" # noqa: E501 + "The Docker Compose project name is resolved in the following order:" "\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." @@ -91,21 +100,21 @@ def main(): 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): + project = get_project_name(args) + if project is None: + 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 + ) + + unhealthy = wait_for_project_health(project, args.timeout) + + if not unhealthy: + print(f"Project {project} is healthy.") return 0 + print( + f"""The {", ".join(map(lambda x: x.name, unhealthy))} container for project {project} are not healthy.""" # noqa: E501 + ) return 1