From a3c1d9977f60de1849a9697d65bfbe2bea1aa1df Mon Sep 17 00:00:00 2001 From: Steve Chavez Date: Fri, 11 Mar 2022 15:16:45 +0100 Subject: [PATCH] Correct live/ready checks to consider special host values (#2182) --- src/PostgREST/Admin.hs | 49 +++++++++++++++++++++++++++++------------- src/PostgREST/App.hs | 2 +- test/io/fixtures.yaml | 7 ++++++ test/io/test_io.py | 15 +++++++++++-- 4 files changed, 55 insertions(+), 18 deletions(-) diff --git a/src/PostgREST/Admin.hs b/src/PostgREST/Admin.hs index 2f2922905..c4a85979f 100644 --- a/src/PostgREST/Admin.hs +++ b/src/PostgREST/Admin.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE RecordWildCards #-} module PostgREST.Admin ( postgrestAdmin ) where @@ -21,7 +22,7 @@ import Protolude -- | PostgREST admin application postgrestAdmin :: AppState.AppState -> AppConfig -> Wai.Application postgrestAdmin appState appConfig req respond = do - isMainAppReachable <- isRight <$> reachMainApp appConfig + isMainAppReachable <- any isRight <$> reachMainApp appConfig isSchemaCacheLoaded <- isJust <$> AppState.getDbStructure appState isConnectionUp <- if configDbChannelEnabled appConfig @@ -38,19 +39,37 @@ postgrestAdmin appState appConfig req respond = do -- Try to connect to the main app socket -- Note that it doesn't even send a valid HTTP request, we just want to check that the main app is accepting connections -reachMainApp :: AppConfig -> IO (Either IOException ()) -reachMainApp appConfig = - try . withSocketsDo $ bracket open close sendEmpty - where - open = case configServerUnixSocket appConfig of - Just path -> do - sock <- socket AF_UNIX Stream 0 +-- The code for resolving the "*4", "!4", "*6", "!6", "*" special values is taken from +-- https://hackage.haskell.org/package/streaming-commons-0.2.2.4/docs/src/Data.Streaming.Network.html#bindPortGenEx +reachMainApp :: AppConfig -> IO [Either IOException ()] +reachMainApp AppConfig{..} = + case configServerUnixSocket of + Just path -> do + sock <- socket AF_UNIX Stream 0 + (:[]) <$> try (do connect sock $ SockAddrUnix path - return sock - Nothing -> do - let hints = defaultHints { addrSocketType = Stream } - addr:_ <- getAddrInfo (Just hints) (Just . T.unpack $ configServerHost appConfig) (Just . show $ configServerPort appConfig) - sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr) - connect sock $ addrAddress addr - return sock + withSocketsDo $ bracket (pure sock) close sendEmpty) + Nothing -> do + let + host | configServerHost `elem` ["*4", "!4", "*6", "!6", "*"] = Nothing + | otherwise = Just configServerHost + filterAddrs xs = + case configServerHost of + "*4" -> ipv4Addrs xs ++ ipv6Addrs xs + "!4" -> ipv4Addrs xs + "*6" -> ipv6Addrs xs ++ ipv4Addrs xs + "!6" -> ipv6Addrs xs + _ -> xs + ipv4Addrs xs = filter ((/=) AF_INET6 . addrFamily) xs + ipv6Addrs xs = filter ((==) AF_INET6 . addrFamily) xs + + addrs <- getAddrInfo (Just $ defaultHints { addrSocketType = Stream }) (T.unpack <$> host) (Just . show $ configServerPort) + tryAddr `traverse` filterAddrs addrs + where sendEmpty sock = void $ send sock mempty + tryAddr :: AddrInfo -> IO (Either IOException ()) + tryAddr addr = do + sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr) + try $ do + connect sock $ addrAddress addr + withSocketsDo $ bracket (pure sock) close sendEmpty diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index 2fc905e44..44f4d43e7 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -131,7 +131,7 @@ run installHandlers maybeRunWithSocket appState = do AppState.logWithZTime appState $ "Listening on unix socket " <> show socket runWithSocket (serverSettings conf) app configServerUnixSocketMode socket Nothing -> - panic "Cannot run with socket on non-unix plattforms." + panic "Cannot run with unix socket on non-unix plattforms." Nothing -> do AppState.logWithZTime appState $ "Listening on port " <> show configServerPort diff --git a/test/io/fixtures.yaml b/test/io/fixtures.yaml index 45f71e245..8aa8993fd 100644 --- a/test/io/fixtures.yaml +++ b/test/io/fixtures.yaml @@ -171,3 +171,10 @@ invalidjointypes: - 'left!' - 'right' - '.#$$%&$%/' + +specialhostvalues: + - '*4' + - '!4' + - '*6' + - '!6' + - '*' diff --git a/test/io/test_io.py b/test/io/test_io.py index dd496f1cb..dd5f43bb0 100644 --- a/test/io/test_io.py +++ b/test/io/test_io.py @@ -140,7 +140,7 @@ def dumpconfig(configpath=None, env=None, stdin=None): @contextlib.contextmanager -def run(configpath=None, stdin=None, env=None, port=None): +def run(configpath=None, stdin=None, env=None, port=None, host=None): "Run PostgREST and yield an endpoint that is ready for connections." env = env or {} env["PGRST_DB_POOL"] = "1" @@ -149,7 +149,7 @@ def run(configpath=None, stdin=None, env=None, port=None): with tempfile.TemporaryDirectory() as tmpdir: if port: env["PGRST_SERVER_PORT"] = str(port) - env["PGRST_SERVER_HOST"] = "localhost" + env["PGRST_SERVER_HOST"] = host or "localhost" baseurl = f"http://localhost:{port}" else: socketfile = pathlib.Path(tmpdir) / "postgrest.sock" @@ -883,6 +883,17 @@ def test_admin_live_dependent_on_main_app(defaultenv): response = postgrest.admin.get("/live") assert response.status_code == 503 +@pytest.mark.parametrize("specialhostvalue", FIXTURES["specialhostvalues"]) +def test_admin_works_with_host_special_values(specialhostvalue, defaultenv): + "Should get a success from the admin live and ready endpoints when using special host values for the main app" + + with run(env=defaultenv, port=freeport(), host=specialhostvalue) as postgrest: + + response = postgrest.admin.get("/live") + assert response.status_code == 200 + + response = postgrest.admin.get("/ready") + assert response.status_code == 200 @pytest.mark.parametrize( "level, has_output",