Skip to content

Commit fc1a5ec

Browse files
committed
add: --ready flag for postgrest healthcheck
The `--ready` flag is a wrapper around the admin server `/ready` request. This is done through using an http client library in postgrest. Signed-off-by: Taimoor Zaeem <taimoorzaeem@gmail.com>
1 parent 08a6a9d commit fc1a5ec

File tree

6 files changed

+156
-5
lines changed

6 files changed

+156
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
1212
+ The exposed schemas are now listed in the `hint` instead of the `message` field.
1313
- Improve error details of `PGRST301` error by @taimoorzaeem in #4051
1414
- Bounded JWT cache using the SIEVE algorithm by @mkleczek in #4084
15+
- Add `--ready` flag for postgrest healthcheck by @taimoorzaeem in #4239
1516

1617
### Fixed
1718

docs/references/cli.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,12 @@ Dump Schema
4949
$ postgrest [--dump-schema]
5050
5151
Dumps the schema cache in JSON format.
52+
53+
Ready Flag
54+
----------
55+
56+
.. code:: bash
57+
58+
$ postgrest [--ready]
59+
60+
This is analogous to the ``/ready`` endpoint in :ref:`admin_server`. However, instead of an HTTP response, this exits with return code of ``0`` on success and ``1`` on failure.

postgrest.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ library
5252
PostgREST.Auth.Types
5353
PostgREST.Cache.Sieve
5454
PostgREST.CLI
55+
PostgREST.Client
5556
PostgREST.Config
5657
PostgREST.Config.Database
5758
PostgREST.Config.JSPath
@@ -117,6 +118,7 @@ library
117118
, hasql-pool >= 1.0.1 && < 1.1
118119
, hasql-transaction >= 1.0.1 && < 1.2
119120
, heredoc >= 0.2 && < 0.3
121+
, http-client >= 0.7.19 && < 0.8
120122
, http-types >= 0.12.2 && < 0.13
121123
, insert-ordered-containers >= 0.2.2 && < 0.3
122124
, iproute >= 1.7.0 && < 1.8

src/PostgREST/CLI.hs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE LambdaCase #-}
12
{-# LANGUAGE NamedFieldPuns #-}
23
{-# LANGUAGE QuasiQuotes #-}
34
{-# LANGUAGE RecordWildCards #-}
@@ -12,6 +13,7 @@ import qualified Data.Aeson as JSON
1213
import qualified Data.ByteString.Char8 as BS
1314
import qualified Data.ByteString.Lazy as LBS
1415
import qualified Hasql.Transaction.Sessions as SQL
16+
import qualified Network.HTTP.Types.Status as HTTP
1517
import qualified Options.Applicative as O
1618

1719
import Text.Heredoc (str)
@@ -24,23 +26,36 @@ import PostgREST.Version (prettyVersion)
2426

2527
import qualified PostgREST.App as App
2628
import qualified PostgREST.AppState as AppState
29+
import qualified PostgREST.Client as Client
2730
import qualified PostgREST.Config as Config
2831

2932
import Protolude
3033

3134

3235
main :: CLI -> IO ()
3336
main CLI{cliCommand, cliPath} = do
34-
conf@AppConfig{..} <-
37+
conf <-
3538
either panic identity <$> Config.readAppConfig mempty cliPath Nothing mempty mempty
39+
case cliCommand of
40+
Admin adminCmd -> runAdminCommand conf adminCmd
41+
Run runCmd -> runAppCommand conf runCmd
3642

43+
-- | Run command using http-client to communicate with an already running postgrest
44+
runAdminCommand :: AppConfig -> AdminCommand -> IO ()
45+
runAdminCommand conf CmdReady = Client.ready conf >>= \case
46+
status | status >= HTTP.status200 && status < HTTP.status300 -> exitSuccess
47+
| otherwise -> exitWith $ ExitFailure 1
48+
49+
-- | Run postgrest with command
50+
runAppCommand :: AppConfig -> RunCommand -> IO ()
51+
runAppCommand conf@AppConfig{..} runCmd = do
3752
-- Per https://github.com/PostgREST/postgrest/issues/268, we want to
3853
-- explicitly close the connections to PostgreSQL on shutdown.
3954
-- 'AppState.destroy' takes care of that.
4055
bracket
4156
(AppState.init conf)
4257
AppState.destroy
43-
(\appState -> case cliCommand of
58+
(\appState -> case runCmd of
4459
CmdDumpConfig -> do
4560
when configDbConfig $ AppState.readInDbConfig True appState
4661
putStr . Config.toText =<< AppState.getConfig appState
@@ -71,6 +86,13 @@ data CLI = CLI
7186
}
7287

7388
data Command
89+
= Admin AdminCommand
90+
| Run RunCommand
91+
92+
data AdminCommand
93+
= CmdReady
94+
95+
data RunCommand
7496
= CmdRun
7597
| CmdDumpConfig
7698
| CmdDumpSchema
@@ -105,7 +127,7 @@ readCLIShowHelp =
105127
cliParser :: O.Parser CLI
106128
cliParser =
107129
CLI
108-
<$> (dumpConfigFlag <|> dumpSchemaFlag)
130+
<$> (dumpConfigFlag <|> dumpSchemaFlag <|> readyFlag)
109131
<*> O.optional configFileOption
110132

111133
configFileOption =
@@ -114,15 +136,20 @@ readCLIShowHelp =
114136
<> O.help "Path to configuration file"
115137

116138
dumpConfigFlag =
117-
O.flag CmdRun CmdDumpConfig $
139+
O.flag (Run CmdRun) (Run CmdDumpConfig) $
118140
O.long "dump-config"
119141
<> O.help "Dump loaded configuration and exit"
120142

121143
dumpSchemaFlag =
122-
O.flag CmdRun CmdDumpSchema $
144+
O.flag (Run CmdRun) (Run CmdDumpSchema) $
123145
O.long "dump-schema"
124146
<> O.help "Dump loaded schema as JSON and exit (for debugging, output structure is unstable)"
125147

148+
readyFlag =
149+
O.flag (Run CmdRun) (Admin CmdReady) $
150+
O.long "ready"
151+
<> O.help "Health check for if PostgREST is running and ready"
152+
126153
exampleConfigFile :: [Char]
127154
exampleConfigFile =
128155
[str|## Admin server used for checks. It's disabled by default unless a port is specified.

src/PostgREST/Client.hs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{-# LANGUAGE NamedFieldPuns #-}
2+
module PostgREST.Client
3+
( ready
4+
) where
5+
6+
import qualified Data.Text as T
7+
import qualified Network.HTTP.Client as HC
8+
9+
import Network.HTTP.Types.Status (Status (..))
10+
import PostgREST.Config (AppConfig (..))
11+
12+
import Protolude
13+
14+
ready :: AppConfig -> IO Status
15+
ready AppConfig{configAdminServerHost, configAdminServerPort} = do
16+
17+
client <- HC.newManager HC.defaultManagerSettings
18+
req <- HC.parseRequest $ -- Create HTTP Request
19+
case configAdminServerPort of
20+
Just port -> T.unpack (address port) <> "/ready"
21+
Nothing -> panic "Admin Server is not running. Please check your configuration."
22+
23+
resp <- HC.httpLbs req client -- HTTP Response
24+
return $ HC.responseStatus resp
25+
where
26+
address port = "http://"
27+
<> escapeHostName configAdminServerHost
28+
<> ":"
29+
<> show port
30+
31+
escapeHostName :: Text -> Text
32+
escapeHostName "*" = "0.0.0.0"
33+
escapeHostName "*4" = "0.0.0.0"
34+
escapeHostName "!4" = "0.0.0.0"
35+
escapeHostName "*6" = "0.0.0.0"
36+
escapeHostName "!6" = "0.0.0.0"
37+
escapeHostName h = h

test/io/test_cli.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,3 +289,78 @@ def test_jwt_aud_config_set_to_invalid_uri(defaultenv):
289289
with pytest.raises(PostgrestError):
290290
dump = cli(["--dump-config"], env=env).split("\n")
291291
assert "jwt-aud should be a string or a valid URI" in dump
292+
293+
294+
def test_cli_ready_flag_success(defaultenv):
295+
"PostgREST ready flag succeeds when ready"
296+
297+
env = {
298+
**defaultenv,
299+
"PGRST_ADMIN_SERVER_PORT": "3001",
300+
}
301+
302+
# Run postgrest process
303+
command = [POSTGREST_BIN]
304+
env["HPCTIXFILE"] = hpctixfile()
305+
pgrst_process = subprocess.Popen(command, env=env)
306+
307+
time.sleep(5) # Wait some seconds for the first process to start
308+
try:
309+
# Run healthcheck process
310+
command = [POSTGREST_BIN] + ["--ready"]
311+
healthcheck_process = subprocess.run(command, env=env, capture_output=True)
312+
assert healthcheck_process.returncode == 0
313+
finally:
314+
pgrst_process.kill()
315+
pgrst_process.wait()
316+
317+
318+
def test_cli_ready_flag_fail(defaultenv):
319+
"PostgREST ready flag fails when not ready"
320+
321+
env = {
322+
**defaultenv,
323+
"PGRST_ADMIN_SERVER_PORT": "3001",
324+
}
325+
326+
# Run postgrest process
327+
command = [POSTGREST_BIN]
328+
env["HPCTIXFILE"] = hpctixfile()
329+
pgrst_process = subprocess.Popen(command, env=env)
330+
331+
time.sleep(0) # No waiting and instantly run the healthcheck process
332+
try:
333+
# Run healthcheck process
334+
command = [POSTGREST_BIN] + ["--ready"]
335+
healthcheck_process = subprocess.run(command, env=env, capture_output=True)
336+
assert healthcheck_process.returncode == 1
337+
finally:
338+
pgrst_process.kill()
339+
pgrst_process.wait()
340+
341+
342+
def test_cli_ready_flag_fail_no_admin_server(defaultenv):
343+
"PostgREST --ready flag fail without admin server running"
344+
345+
env = {
346+
**defaultenv,
347+
"PGRST_ADMIN_SERVER_PORT": "",
348+
}
349+
350+
command = [POSTGREST_BIN]
351+
env["HPCTIXFILE"] = hpctixfile()
352+
pgrst_process = subprocess.Popen(command, env=env)
353+
354+
time.sleep(3) # Wait so postgrest gets ready
355+
try:
356+
# Run healthcheck process
357+
command = [POSTGREST_BIN] + ["--ready"]
358+
healthcheck_process = subprocess.run(command, env=env, capture_output=True)
359+
assert healthcheck_process.returncode == 1
360+
assert (
361+
"Admin Server is not running. Please check your configuration."
362+
in healthcheck_process.stderr.decode()
363+
)
364+
finally:
365+
pgrst_process.kill()
366+
pgrst_process.wait()

0 commit comments

Comments
 (0)