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

services: support verbose mode on healthchecks #11

Closed
wants to merge 2 commits into from
Closed
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
25 changes: 20 additions & 5 deletions docker_services_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@
class ServicesCtx(object):
"""Context class for docker services cli."""

def __init__(self, filepath):
def __init__(self, filepath, verbose):
"""Constructor."""
self.filepath = filepath
self.verbose = verbose


@click.group()
Expand All @@ -35,11 +36,17 @@ def __init__(self, filepath):
type=click.Path(exists=True),
help="Path to a docker compose file with the desired services definition.",
)
@click.option(
"--verbose",
is_flag=True,
default=False,
help="Verbose output.",
)
@click.pass_context
def cli(ctx, filepath):
def cli(ctx, filepath, verbose):
"""Initialize CLI context."""
set_env()
ctx.obj = ServicesCtx(filepath=filepath)
ctx.obj = ServicesCtx(filepath=filepath, verbose=verbose)

@cli.command()
@click.argument("services", nargs=-1, required=False) # -1 incompat with default
Expand All @@ -48,8 +55,14 @@ def cli(ctx, filepath):
is_flag=True,
help="Wait for services to be up (use healthchecks).",
)
@click.option(
'--retries',
default=6,
type=int,
help="Number of times to retry a service's healthcheck."
)
@click.pass_obj
def up(services_ctx, services, no_wait):
def up(services_ctx, services, no_wait, retries):
"""Boots up the required services."""
_services = list(services)

Expand All @@ -64,7 +77,9 @@ def up(services_ctx, services, no_wait):
services_up(
services=_services,
filepath=services_ctx.filepath,
wait=(not no_wait)
wait=(not no_wait),
retries=retries,
verbose=services_ctx.verbose
)
click.secho("Services up!", fg="green")

Expand Down
54 changes: 34 additions & 20 deletions docker_services_cli/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,48 @@

"""Services module."""

import subprocess
from subprocess import check_call, Popen, PIPE
import time

import click

from .config import DOCKER_SERVICES_FILEPATH, MYSQL, POSTGRESQL


def _run_healthcheck_command(command):
def _run_healthcheck_command(command, verbose=False):
"""Runs a given command, returns True if it succeeds, False otherwise."""
try:
subprocess.check_call(
command,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
p = Popen(command, stdout=PIPE, stderr=PIPE)
output, error = p.communicate()
output = output.decode("utf-8")
error = error.decode("utf-8")
if p.returncode == 0:
if verbose:
click.secho(output, fg="green")
return True
except subprocess.CalledProcessError:
if p.returncode != 0:
if verbose:
click.secho(
f"Healthcheck failed.\nOutput: {output}\nError:{error}",
fg="red"
)
return False


def es_healthcheck(*args, **kwargs):
"""Elasticsearch healthcheck."""
verbose = kwargs['verbose']

return _run_healthcheck_command([
"curl",
"-f",
"localhost:9200/_cluster/health?wait_for_status=green"
])
], verbose)


def postgresql_healthcheck(*args, **kwargs):
"""Postgresql healthcheck."""
filepath = kwargs['filepath']
verbose = kwargs['verbose']

return _run_healthcheck_command([
"docker-compose",
Expand All @@ -50,12 +59,13 @@ def postgresql_healthcheck(*args, **kwargs):
"bash",
"-c",
"pg_isready",
])
], verbose)


def mysql_healthcheck(*args, **kwargs):
"""Mysql healthcheck."""
filepath = kwargs['filepath']
verbose = kwargs['verbose']
password = MYSQL["MYSQL_ROOT_PASSWORD"]

return _run_healthcheck_command([
Expand All @@ -67,12 +77,13 @@ def mysql_healthcheck(*args, **kwargs):
"bash",
"-c",
f"mysql -p{password} -e \"select Version();\"",
])
], verbose)


def redis_healthcheck(*args, **kwargs):
"""Redis healthcheck."""
filepath = kwargs['filepath']
verbose = kwargs['verbose']

return _run_healthcheck_command([
"docker-compose",
Expand All @@ -86,7 +97,7 @@ def redis_healthcheck(*args, **kwargs):
"|",
"grep 'PONG'",
"&>/dev/null;",
])
], verbose)


HEALTHCHECKS = {
Expand All @@ -98,7 +109,8 @@ def redis_healthcheck(*args, **kwargs):
"""Health check functions module path, as string."""


def wait_for_services(services, filepath=DOCKER_SERVICES_FILEPATH, max_retries=6):
def wait_for_services(services, filepath=DOCKER_SERVICES_FILEPATH,
max_retries=6, verbose=False):
"""Wait for services to be up.

It performs configured healthchecks in a serial fashion, following the
Expand All @@ -114,7 +126,7 @@ def wait_for_services(services, filepath=DOCKER_SERVICES_FILEPATH, max_retries=6
try_ = 1
# Using plain __import__ to avoid depending on invenio-base
check = HEALTHCHECKS[service]
ready = check(filepath=filepath)
ready = check(filepath=filepath, verbose=verbose)
while not ready and try_ < max_retries:
click.secho(
f"{service} not ready at {try_} retries, waiting " \
Expand All @@ -124,7 +136,7 @@ def wait_for_services(services, filepath=DOCKER_SERVICES_FILEPATH, max_retries=6
try_ += 1
time.sleep(exp_backoff_time)
exp_backoff_time *= 2
ready = check(filepath=filepath)
ready = check(filepath=filepath, verbose=verbose)

if not ready:
click.secho(f"Unable to boot up {service}", fg="red")
Expand All @@ -133,7 +145,8 @@ def wait_for_services(services, filepath=DOCKER_SERVICES_FILEPATH, max_retries=6
click.secho(f"{service} up and running!", fg="green")


def services_up(services, filepath=DOCKER_SERVICES_FILEPATH, wait=True):
def services_up(services, filepath=DOCKER_SERVICES_FILEPATH, wait=True,
retries=6, verbose=False):
"""Start the given services up.

docker-compose is smart about not rebuilding an image if
Expand All @@ -143,9 +156,10 @@ def services_up(services, filepath=DOCKER_SERVICES_FILEPATH, wait=True):
command = ["docker-compose", "--file", filepath, "up", "-d"]
command.extend(services)

subprocess.check_call(command)
check_call(command)
if wait:
wait_for_services(services, filepath)
wait_for_services(services, filepath, max_retries=retries,
verbose=verbose)


def services_down(filepath=DOCKER_SERVICES_FILEPATH):
Expand All @@ -156,4 +170,4 @@ def services_down(filepath=DOCKER_SERVICES_FILEPATH):
"""
command = ["docker-compose", "--file", filepath, "down"]

subprocess.check_call(command)
check_call(command)
2 changes: 1 addition & 1 deletion docker_services_cli/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@
and parsed by ``setup.py``.
"""

__version__ = "0.2.0"
__version__ = "0.2.1"