Skip to content

Commit c9c24eb

Browse files
committed
feat: Add flagsmith healthcheck command
1 parent 49268aa commit c9c24eb

File tree

4 files changed

+129
-4
lines changed

4 files changed

+129
-4
lines changed

src/common/core/cli/__init__.py

Whitespace-only changes.

src/common/core/cli/healthcheck.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import argparse
2+
import socket
3+
4+
5+
def get_args(argv: list[str]) -> argparse.Namespace:
6+
parser = argparse.ArgumentParser(
7+
description="Check if the API is able to accept local TCP connections.",
8+
)
9+
parser.add_argument(
10+
"--port",
11+
"-p",
12+
type=int,
13+
default=8000,
14+
help="Port to check the API on (default: 8000)",
15+
)
16+
parser.add_argument(
17+
"--timeout",
18+
"-t",
19+
type=int,
20+
default=1,
21+
help="Socket timeout for the connection attempt in seconds (default: 1)",
22+
)
23+
return parser.parse_args(argv)
24+
25+
26+
def main(argv: list[str]) -> None:
27+
args = get_args(argv)
28+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
29+
sock.settimeout(args.timeout)
30+
try:
31+
sock.connect(("localhost", args.port))
32+
except socket.error as e:
33+
sock.close()
34+
print(f"Failed: {e} {args.port=}")
35+
exit(1)
36+
else:
37+
sock.close()
38+
print("OK")
39+
exit(0)

src/common/core/main.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
import tempfile
66
import typing
77

8-
from django.core.management import execute_from_command_line
8+
from django.core.management import (
9+
execute_from_command_line as django_execute_from_command_line,
10+
)
11+
12+
from common.core.cli import healthcheck
913

1014
logger = logging.getLogger(__name__)
1115

@@ -56,7 +60,19 @@ def ensure_cli_env() -> typing.Generator[None, None, None]:
5660
yield
5761

5862

59-
def main() -> None:
63+
def execute_from_command_line(argv: list[str]) -> None:
64+
try:
65+
subcommand = argv[1]
66+
subcommand_main = {
67+
"healthcheck": healthcheck.main,
68+
}[subcommand]
69+
except (IndexError, KeyError):
70+
django_execute_from_command_line(argv)
71+
else:
72+
subcommand_main(argv[2:])
73+
74+
75+
def main(argv: list[str] = sys.argv) -> None:
6076
"""
6177
The main entry point to the Flagsmith application.
6278
@@ -72,5 +88,5 @@ def main() -> None:
7288
`flagsmith <command> [options]`
7389
"""
7490
with ensure_cli_env():
75-
# Run Django
76-
execute_from_command_line(sys.argv)
91+
# Run own commands and Django
92+
execute_from_command_line(argv)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import io
2+
import subprocess
3+
4+
import django
5+
import pytest
6+
from django.core.management import ManagementUtility
7+
8+
from common.core.main import main
9+
10+
11+
@pytest.mark.parametrize(
12+
"argv, expected_out",
13+
(
14+
[["flagsmith"], ManagementUtility().main_help_text()],
15+
[["flagsmith", "version"], django.get_version()],
16+
),
17+
)
18+
def test_main__non_overridden_args__defaults_to_django(
19+
argv: list[str],
20+
expected_out: str,
21+
capsys: pytest.CaptureFixture[str],
22+
) -> None:
23+
# Given & When
24+
main(argv)
25+
26+
# Then
27+
assert capsys.readouterr().out[:-1] == expected_out
28+
29+
30+
def test_main__healthcheck__no_server__runs_expected(
31+
capsys: pytest.CaptureFixture[str],
32+
) -> None:
33+
# Given
34+
argv = ["flagsmith", "healthcheck"]
35+
36+
# When
37+
with pytest.raises(SystemExit) as exc_info:
38+
main(argv)
39+
40+
# Then
41+
assert exc_info.value.code == 1
42+
assert (
43+
capsys.readouterr().out[:-1]
44+
== "Failed: [Errno 61] Connection refused args.port=8000"
45+
)
46+
47+
48+
def test_main__healtcheck__server__runs_expected(
49+
unused_tcp_port: int,
50+
capsys: pytest.CaptureFixture[str],
51+
) -> None:
52+
# Given
53+
process = subprocess.Popen(
54+
["flagsmith", "start", "-b", f"0.0.0.0:{unused_tcp_port}", "api"],
55+
stderr=subprocess.PIPE,
56+
)
57+
assert process.stderr
58+
for line in io.TextIOWrapper(process.stderr, "utf-8"):
59+
if "Listening" in line:
60+
break
61+
62+
argv = ["flagsmith", "healthcheck", "--port", str(unused_tcp_port)]
63+
64+
# When
65+
with pytest.raises(SystemExit) as exc_info:
66+
main(argv)
67+
68+
# Then
69+
assert exc_info.value.code == 0
70+
assert capsys.readouterr().out[:-1] == "OK"

0 commit comments

Comments
 (0)