Allow config file reloading with SIGUSR2
* move config validation to Config.hs * Add tests for jwt-secret/app.settings.*/db-schema reload
This commit is contained in:
committed by
Steve Chavez
parent
e272ea47be
commit
e8b4e3771c
@@ -9,6 +9,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
- #1525, Allow http status override through response.status guc - @steve-chavez
|
- #1525, Allow http status override through response.status guc - @steve-chavez
|
||||||
- #1512, Allow schema cache reloading with NOTIFY - @steve-chavez
|
- #1512, Allow schema cache reloading with NOTIFY - @steve-chavez
|
||||||
|
- #1119, Allow config file reloading with SIGUSR2 - @steve-chavez
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
+37
-38
@@ -17,7 +17,6 @@ import Control.Debounce (debounceAction, debounceEdge,
|
|||||||
import Control.Retry (RetryStatus, capDelay,
|
import Control.Retry (RetryStatus, capDelay,
|
||||||
exponentialBackoff, retrying,
|
exponentialBackoff, retrying,
|
||||||
rsPreviousDelay)
|
rsPreviousDelay)
|
||||||
import Data.Either.Combinators (whenLeft)
|
|
||||||
import Data.IORef (IORef, atomicWriteIORef, newIORef,
|
import Data.IORef (IORef, atomicWriteIORef, newIORef,
|
||||||
readIORef)
|
readIORef)
|
||||||
import Data.String (IsString (..))
|
import Data.String (IsString (..))
|
||||||
@@ -28,15 +27,12 @@ import Network.Wai.Handler.Warp (defaultSettings, runSettings,
|
|||||||
import System.IO (BufferMode (..), hSetBuffering)
|
import System.IO (BufferMode (..), hSetBuffering)
|
||||||
|
|
||||||
import PostgREST.App (postgrest)
|
import PostgREST.App (postgrest)
|
||||||
import PostgREST.Auth (parseSecret)
|
|
||||||
import PostgREST.Config (AppConfig (..), configPoolTimeout',
|
import PostgREST.Config (AppConfig (..), configPoolTimeout',
|
||||||
loadDbUriFile, loadSecretFile,
|
prettyVersion, readPathShowHelp,
|
||||||
prettyVersion, readAppConfig,
|
readValidateConfig)
|
||||||
readPathShowHelp)
|
|
||||||
import PostgREST.DbStructure (getDbStructure, getPgVersion)
|
import PostgREST.DbStructure (getDbStructure, getPgVersion)
|
||||||
import PostgREST.Error (PgError (PgError), checkIsFatal,
|
import PostgREST.Error (PgError (PgError), checkIsFatal,
|
||||||
errorPayload)
|
errorPayload)
|
||||||
import PostgREST.OpenAPI (isMalformedProxyUri)
|
|
||||||
import PostgREST.Types (ConnectionStatus (..), DbStructure,
|
import PostgREST.Types (ConnectionStatus (..), DbStructure,
|
||||||
LogSetup (..), PgVersion (..),
|
LogSetup (..), PgVersion (..),
|
||||||
minimumPgVersion)
|
minimumPgVersion)
|
||||||
@@ -75,12 +71,12 @@ _1s = 1000000 :: Int -- 1 second
|
|||||||
connectionWorker
|
connectionWorker
|
||||||
:: ThreadId -- ^ Main thread id. Killed if pg version is unsupported
|
:: ThreadId -- ^ Main thread id. Killed if pg version is unsupported
|
||||||
-> P.Pool -- ^ The PostgreSQL connection pool
|
-> P.Pool -- ^ The PostgreSQL connection pool
|
||||||
-> AppConfig
|
-> IORef AppConfig
|
||||||
-> IORef (Maybe DbStructure) -- ^ mutable reference to 'DbStructure'
|
-> IORef (Maybe DbStructure) -- ^ mutable reference to 'DbStructure'
|
||||||
-> IORef Bool -- ^ Used as a binary Semaphore
|
-> IORef Bool -- ^ Used as a binary Semaphore
|
||||||
-> (Bool, MVar ConnectionStatus) -- ^ For interacting with the LISTEN channel
|
-> (Bool, MVar ConnectionStatus) -- ^ For interacting with the LISTEN channel
|
||||||
-> IO ()
|
-> IO ()
|
||||||
connectionWorker mainTid pool conf refDbStructure refIsWorkerOn (dbChannelEnabled, mvarConnectionStatus) = do
|
connectionWorker mainTid pool refConf refDbStructure refIsWorkerOn (dbChannelEnabled, mvarConnectionStatus) = do
|
||||||
isWorkerOn <- readIORef refIsWorkerOn
|
isWorkerOn <- readIORef refIsWorkerOn
|
||||||
unless isWorkerOn $ do -- Prevents multiple workers to be running at the same time. Could happen on too many SIGUSR1s.
|
unless isWorkerOn $ do -- Prevents multiple workers to be running at the same time. Could happen on too many SIGUSR1s.
|
||||||
atomicWriteIORef refIsWorkerOn True
|
atomicWriteIORef refIsWorkerOn True
|
||||||
@@ -97,12 +93,13 @@ connectionWorker mainTid pool conf refDbStructure refIsWorkerOn (dbChannelEnable
|
|||||||
NotConnected -> return () -- Unreachable because connectionStatus will keep trying to connect
|
NotConnected -> return () -- Unreachable because connectionStatus will keep trying to connect
|
||||||
Connected actualPgVersion -> do -- Procede with initialization
|
Connected actualPgVersion -> do -- Procede with initialization
|
||||||
putStrLn ("Connection successful" :: Text)
|
putStrLn ("Connection successful" :: Text)
|
||||||
fillSchemaCache pool actualPgVersion conf refDbStructure
|
fillSchemaCache pool actualPgVersion refConf refDbStructure
|
||||||
liftIO $ atomicWriteIORef refIsWorkerOn False
|
liftIO $ atomicWriteIORef refIsWorkerOn False
|
||||||
|
|
||||||
fillSchemaCache :: P.Pool -> PgVersion -> AppConfig -> IORef (Maybe DbStructure) -> IO ()
|
fillSchemaCache :: P.Pool -> PgVersion -> IORef AppConfig -> IORef (Maybe DbStructure) -> IO ()
|
||||||
fillSchemaCache pool actualPgVersion conf refDbStructure = do
|
fillSchemaCache pool actualPgVersion refConf refDbStructure = do
|
||||||
result <- P.use pool $ HT.transaction HT.ReadCommitted HT.Read $ getDbStructure schemas actualPgVersion
|
conf <- readIORef refConf
|
||||||
|
result <- P.use pool $ HT.transaction HT.ReadCommitted HT.Read $ getDbStructure (toList $ configSchemas conf) actualPgVersion
|
||||||
case result of
|
case result of
|
||||||
Left e -> do
|
Left e -> do
|
||||||
-- If this error happens it would mean the connection is down again. Improbable because connectionStatus ensured the connection.
|
-- If this error happens it would mean the connection is down again. Improbable because connectionStatus ensured the connection.
|
||||||
@@ -113,7 +110,6 @@ fillSchemaCache pool actualPgVersion conf refDbStructure = do
|
|||||||
Right dbStructure -> do
|
Right dbStructure -> do
|
||||||
atomicWriteIORef refDbStructure $ Just dbStructure
|
atomicWriteIORef refDbStructure $ Just dbStructure
|
||||||
putStrLn ("Schema cache loaded" :: Text)
|
putStrLn ("Schema cache loaded" :: Text)
|
||||||
where schemas = toList $ configSchemas conf
|
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
Used by 'connectionWorker' to check if the provided db-uri lets
|
Used by 'connectionWorker' to check if the provided db-uri lets
|
||||||
@@ -159,8 +155,8 @@ connectionStatus pool =
|
|||||||
When a NOTIFY channel(with an empty payload) is done, it refills the schema cache.
|
When a NOTIFY channel(with an empty payload) is done, it refills the schema cache.
|
||||||
It uses the connectionWorker in case the LISTEN connection dies.
|
It uses the connectionWorker in case the LISTEN connection dies.
|
||||||
-}
|
-}
|
||||||
listener :: ByteString -> Text -> P.Pool -> AppConfig -> IORef (Maybe DbStructure) -> MVar ConnectionStatus -> IO () -> IO ()
|
listener :: ByteString -> Text -> P.Pool -> IORef AppConfig -> IORef (Maybe DbStructure) -> MVar ConnectionStatus -> IO () -> IO ()
|
||||||
listener dbUri dbChannel pool conf refDbStructure mvarConnectionStatus connWorker = start
|
listener dbUri dbChannel pool refConf refDbStructure mvarConnectionStatus connWorker = start
|
||||||
where
|
where
|
||||||
start = do
|
start = do
|
||||||
connStatus <- takeMVar mvarConnectionStatus -- takeMVar makes the thread wait if the MVar is empty(until there's a connection).
|
connStatus <- takeMVar mvarConnectionStatus -- takeMVar makes the thread wait if the MVar is empty(until there's a connection).
|
||||||
@@ -169,7 +165,7 @@ listener dbUri dbChannel pool conf refDbStructure mvarConnectionStatus connWorke
|
|||||||
dbOrError <- C.acquire dbUri
|
dbOrError <- C.acquire dbUri
|
||||||
-- Debounce in case too many NOTIFYs arrive. Could happen on a migration(assuming a pg EVENT TRIGGER is set up).
|
-- Debounce in case too many NOTIFYs arrive. Could happen on a migration(assuming a pg EVENT TRIGGER is set up).
|
||||||
scFiller <- mkDebounce (defaultDebounceSettings {
|
scFiller <- mkDebounce (defaultDebounceSettings {
|
||||||
debounceAction = fillSchemaCache pool actualPgVersion conf refDbStructure,
|
debounceAction = fillSchemaCache pool actualPgVersion refConf refDbStructure,
|
||||||
debounceEdge = trailingEdge, -- wait until the function hasn’t been called in _1s
|
debounceEdge = trailingEdge, -- wait until the function hasn’t been called in _1s
|
||||||
debounceFreq = _1s })
|
debounceFreq = _1s })
|
||||||
case dbOrError of
|
case dbOrError of
|
||||||
@@ -191,6 +187,14 @@ listener dbUri dbChannel pool conf refDbStructure mvarConnectionStatus connWorke
|
|||||||
errorMessage = "Could not listen for notifications on the " <> dbChannel <> " channel" :: Text
|
errorMessage = "Could not listen for notifications on the " <> dbChannel <> " channel" :: Text
|
||||||
retryMessage = "Retrying listening for notifications on the " <> dbChannel <> " channel.." :: Text
|
retryMessage = "Retrying listening for notifications on the " <> dbChannel <> " channel.." :: Text
|
||||||
|
|
||||||
|
-- | Re-reads the config at runtime. Invoked on SIGUSR2.
|
||||||
|
-- | If it panics(config path was changed, invalid setting), it'll show an error but won't kill the main thread.
|
||||||
|
reReadConfig :: FilePath -> IORef AppConfig -> IO ()
|
||||||
|
reReadConfig path refConf = do
|
||||||
|
conf <- readValidateConfig path
|
||||||
|
atomicWriteIORef refConf conf
|
||||||
|
putStrLn ("Config file reloaded" :: Text)
|
||||||
|
|
||||||
-- | This is where everything starts.
|
-- | This is where everything starts.
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
@@ -207,19 +211,9 @@ main = do
|
|||||||
path <- readPathShowHelp
|
path <- readPathShowHelp
|
||||||
|
|
||||||
-- build the 'AppConfig' from the config file path
|
-- build the 'AppConfig' from the config file path
|
||||||
conf <- do
|
conf <- readValidateConfig path
|
||||||
cnf <- loadDbUriFile =<< loadSecretFile =<< readAppConfig path
|
|
||||||
pure cnf { configJWKS = parseSecret <$> configJwtSecret cnf}
|
|
||||||
|
|
||||||
-- Checks that the provided proxy uri is formated correctly
|
-- These are config values that can't be reloaded at runtime. Reloading some of them would imply restarting the web server.
|
||||||
when (isMalformedProxyUri $ toS <$> configOpenAPIProxyUri conf) $
|
|
||||||
panic
|
|
||||||
"Malformed proxy uri, a correct example: https://example.com:8443/basePath"
|
|
||||||
|
|
||||||
-- Checks that the provided jspath is valid
|
|
||||||
whenLeft (configRoleClaimKey conf) panic
|
|
||||||
|
|
||||||
-- These are config values that can't be reloaded with SIGUSR2
|
|
||||||
let
|
let
|
||||||
host = configHost conf
|
host = configHost conf
|
||||||
port = configPort conf
|
port = configPort conf
|
||||||
@@ -227,7 +221,7 @@ main = do
|
|||||||
socketFileMode = configSocketMode conf
|
socketFileMode = configSocketMode conf
|
||||||
dbUri = toS (configDbUri conf)
|
dbUri = toS (configDbUri conf)
|
||||||
(dbChannelEnabled, dbChannel) = (configDbChannelEnabled conf, toS $ configDbChannel conf)
|
(dbChannelEnabled, dbChannel) = (configDbChannelEnabled conf, toS $ configDbChannel conf)
|
||||||
appSettings =
|
serverSettings =
|
||||||
setHost ((fromString . toS) host) -- Warp settings
|
setHost ((fromString . toS) host) -- Warp settings
|
||||||
. setPort port
|
. setPort port
|
||||||
. setServerName (toS $ "postgrest/" <> prettyVersion) $
|
. setServerName (toS $ "postgrest/" <> prettyVersion) $
|
||||||
@@ -235,9 +229,6 @@ main = do
|
|||||||
poolSize = configPoolSize conf
|
poolSize = configPoolSize conf
|
||||||
poolTimeout = configPoolTimeout' conf
|
poolTimeout = configPoolTimeout' conf
|
||||||
|
|
||||||
-- Check the file mode is valid
|
|
||||||
whenLeft socketFileMode panic
|
|
||||||
|
|
||||||
-- create connection pool with the provided settings, returns either
|
-- create connection pool with the provided settings, returns either
|
||||||
-- a 'Connection' or a 'ConnectionError'. Does not throw.
|
-- a 'Connection' or a 'ConnectionError'. Does not throw.
|
||||||
pool <- P.acquire (poolSize, poolTimeout, dbUri)
|
pool <- P.acquire (poolSize, poolTimeout, dbUri)
|
||||||
@@ -251,11 +242,14 @@ main = do
|
|||||||
-- Helper ref to make sure just one connectionWorker can run at a time
|
-- Helper ref to make sure just one connectionWorker can run at a time
|
||||||
refIsWorkerOn <- newIORef False
|
refIsWorkerOn <- newIORef False
|
||||||
|
|
||||||
|
-- Config that can change at runtime
|
||||||
|
refConf <- newIORef conf
|
||||||
|
|
||||||
-- This is passed to the connectionWorker method so it can kill the main
|
-- This is passed to the connectionWorker method so it can kill the main
|
||||||
-- thread if the PostgreSQL's version is not supported.
|
-- thread if the PostgreSQL's version is not supported.
|
||||||
mainTid <- myThreadId
|
mainTid <- myThreadId
|
||||||
|
|
||||||
let connWorker = connectionWorker mainTid pool conf refDbStructure refIsWorkerOn (dbChannelEnabled, mvarConnectionStatus)
|
let connWorker = connectionWorker mainTid pool refConf refDbStructure refIsWorkerOn (dbChannelEnabled, mvarConnectionStatus)
|
||||||
|
|
||||||
-- Sets the initial refDbStructure
|
-- Sets the initial refDbStructure
|
||||||
connWorker
|
connWorker
|
||||||
@@ -276,11 +270,16 @@ main = do
|
|||||||
void $ installHandler sigUSR1 (
|
void $ installHandler sigUSR1 (
|
||||||
Catch connWorker
|
Catch connWorker
|
||||||
) Nothing
|
) Nothing
|
||||||
|
|
||||||
|
-- Re-read the config on SIGUSR2
|
||||||
|
void $ installHandler sigUSR2 (
|
||||||
|
Catch $ reReadConfig path refConf
|
||||||
|
) Nothing
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
-- reload schema cache on NOTIFY
|
-- reload schema cache on NOTIFY
|
||||||
when dbChannelEnabled $
|
when dbChannelEnabled $
|
||||||
listener dbUri dbChannel pool conf refDbStructure mvarConnectionStatus connWorker
|
listener dbUri dbChannel pool refConf refDbStructure mvarConnectionStatus connWorker
|
||||||
|
|
||||||
-- ask for the OS time at most once per second
|
-- ask for the OS time at most once per second
|
||||||
getTime <- mkAutoUpdate defaultUpdateSettings {updateAction = getCurrentTime}
|
getTime <- mkAutoUpdate defaultUpdateSettings {updateAction = getCurrentTime}
|
||||||
@@ -288,22 +287,22 @@ main = do
|
|||||||
let postgrestApplication =
|
let postgrestApplication =
|
||||||
postgrest
|
postgrest
|
||||||
LogStdout
|
LogStdout
|
||||||
conf
|
refConf
|
||||||
refDbStructure
|
refDbStructure
|
||||||
pool
|
pool
|
||||||
getTime
|
getTime
|
||||||
connWorker
|
connWorker
|
||||||
|
|
||||||
-- run the postgrest application with user defined socket. Only for UNIX systems.
|
|
||||||
#ifndef mingw32_HOST_OS
|
#ifndef mingw32_HOST_OS
|
||||||
|
-- run the postgrest application with user defined socket. Only for UNIX systems.
|
||||||
whenJust maybeSocketAddr $
|
whenJust maybeSocketAddr $
|
||||||
runAppInSocket appSettings postgrestApplication socketFileMode
|
runAppInSocket serverSettings postgrestApplication socketFileMode
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
-- run the postgrest application
|
-- run the postgrest application
|
||||||
whenNothing maybeSocketAddr $ do
|
whenNothing maybeSocketAddr $ do
|
||||||
putStrLn $ ("Listening on port " :: Text) <> show port
|
putStrLn $ ("Listening on port " :: Text) <> show port
|
||||||
runSettings appSettings postgrestApplication
|
runSettings serverSettings postgrestApplication
|
||||||
|
|
||||||
-- Utilitarian functions.
|
-- Utilitarian functions.
|
||||||
whenJust :: Applicative f => Maybe a -> (a -> f ()) -> f ()
|
whenJust :: Applicative f => Maybe a -> (a -> f ()) -> f ()
|
||||||
|
|||||||
@@ -65,12 +65,13 @@ import PostgREST.Types
|
|||||||
import Protolude hiding (Proxy, intercalate, toS)
|
import Protolude hiding (Proxy, intercalate, toS)
|
||||||
import Protolude.Conv (toS)
|
import Protolude.Conv (toS)
|
||||||
|
|
||||||
postgrest :: LogSetup -> AppConfig -> IORef (Maybe DbStructure) -> P.Pool -> IO UTCTime -> IO () -> Application
|
postgrest :: LogSetup -> IORef AppConfig -> IORef (Maybe DbStructure) -> P.Pool -> IO UTCTime -> IO () -> Application
|
||||||
postgrest logs conf refDbStructure pool getTime worker =
|
postgrest logS refConf refDbStructure pool getTime worker =
|
||||||
pgrstMiddleware logs $ \ req respond -> do
|
pgrstMiddleware logS $ \ req respond -> do
|
||||||
time <- getTime
|
time <- getTime
|
||||||
body <- strictRequestBody req
|
body <- strictRequestBody req
|
||||||
maybeDbStructure <- readIORef refDbStructure
|
maybeDbStructure <- readIORef refDbStructure
|
||||||
|
conf <- readIORef refConf
|
||||||
case maybeDbStructure of
|
case maybeDbStructure of
|
||||||
Nothing -> respond . errorResponseFor $ ConnectionLostError
|
Nothing -> respond . errorResponseFor $ ConnectionLostError
|
||||||
Just dbStructure -> do
|
Just dbStructure -> do
|
||||||
|
|||||||
+22
-6
@@ -19,12 +19,10 @@ Other hardcoded options such as the minimum version number also belong here.
|
|||||||
|
|
||||||
module PostgREST.Config ( prettyVersion
|
module PostgREST.Config ( prettyVersion
|
||||||
, docsVersion
|
, docsVersion
|
||||||
, readPathShowHelp
|
|
||||||
, readAppConfig
|
|
||||||
, AppConfig (..)
|
, AppConfig (..)
|
||||||
, configPoolTimeout'
|
, configPoolTimeout'
|
||||||
, loadSecretFile
|
, readPathShowHelp
|
||||||
, loadDbUriFile
|
, readValidateConfig
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
@@ -32,6 +30,7 @@ import qualified Data.ByteString as B
|
|||||||
import qualified Data.ByteString.Base64 as B64
|
import qualified Data.ByteString.Base64 as B64
|
||||||
import qualified Data.ByteString.Char8 as BS
|
import qualified Data.ByteString.Char8 as BS
|
||||||
import qualified Data.Configurator as C
|
import qualified Data.Configurator as C
|
||||||
|
import Data.Either.Combinators (whenLeft)
|
||||||
import qualified Text.PrettyPrint.ANSI.Leijen as L
|
import qualified Text.PrettyPrint.ANSI.Leijen as L
|
||||||
|
|
||||||
import Control.Lens (preview)
|
import Control.Lens (preview)
|
||||||
@@ -56,10 +55,13 @@ import Options.Applicative hiding (str)
|
|||||||
import Text.Heredoc
|
import Text.Heredoc
|
||||||
import Text.PrettyPrint.ANSI.Leijen hiding ((<$>), (<>))
|
import Text.PrettyPrint.ANSI.Leijen hiding ((<$>), (<>))
|
||||||
|
|
||||||
|
import PostgREST.Auth (parseSecret)
|
||||||
import PostgREST.Parsers (pRoleClaimKey)
|
import PostgREST.Parsers (pRoleClaimKey)
|
||||||
|
import PostgREST.Private.ProxyUri (isMalformedProxyUri)
|
||||||
import PostgREST.Types (JSPath, JSPathExp (..))
|
import PostgREST.Types (JSPath, JSPathExp (..))
|
||||||
import Protolude hiding (concat, hPutStrLn, intercalate, null,
|
import Protolude hiding (concat, hPutStrLn,
|
||||||
replace, take, toS, (<>))
|
intercalate, null, replace, take,
|
||||||
|
toS, (<>))
|
||||||
import Protolude.Conv (toS)
|
import Protolude.Conv (toS)
|
||||||
|
|
||||||
|
|
||||||
@@ -300,6 +302,20 @@ readAppConfig cfgPath = do
|
|||||||
hPutStrLn stderr err
|
hPutStrLn stderr err
|
||||||
exitFailure
|
exitFailure
|
||||||
|
|
||||||
|
-- | Parse the AppConfig and validate it. Panic on invalid config options.
|
||||||
|
readValidateConfig :: FilePath -> IO AppConfig
|
||||||
|
readValidateConfig path = do
|
||||||
|
conf <- loadDbUriFile =<< loadSecretFile =<< readAppConfig path
|
||||||
|
-- Checks that the provided proxy uri is formated correctly
|
||||||
|
when (isMalformedProxyUri $ toS <$> configOpenAPIProxyUri conf) $
|
||||||
|
panic
|
||||||
|
"Malformed proxy uri, a correct example: https://example.com:8443/basePath"
|
||||||
|
-- Checks that the provided jspath is valid
|
||||||
|
whenLeft (configRoleClaimKey conf) panic
|
||||||
|
-- Check the file mode is valid
|
||||||
|
whenLeft (configSocketMode conf) panic
|
||||||
|
return $ conf { configJWKS = parseSecret <$> configJwtSecret conf}
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
The purpose of this function is to load the JWT secret from a file if
|
The purpose of this function is to load the JWT secret from a file if
|
||||||
configJwtSecret is actually a filepath and replaces some characters if the JWT
|
configJwtSecret is actually a filepath and replaces some characters if the JWT
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ Description : Generates the OpenAPI output
|
|||||||
module PostgREST.OpenAPI (
|
module PostgREST.OpenAPI (
|
||||||
encodeOpenAPI
|
encodeOpenAPI
|
||||||
, pickProxy
|
, pickProxy
|
||||||
, isMalformedProxyUri
|
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.HashSet.InsOrd as Set
|
import qualified Data.HashSet.InsOrd as Set
|
||||||
|
|||||||
+5
-2
@@ -65,12 +65,15 @@ main = do
|
|||||||
|
|
||||||
let
|
let
|
||||||
-- For tests that run with the same refDbStructure
|
-- For tests that run with the same refDbStructure
|
||||||
app cfg = return ((), postgrest LogQuiet (cfg testDbConn) refDbStructure pool getTime $ pure ())
|
app cfg = do
|
||||||
|
refConf <- newIORef $ cfg testDbConn
|
||||||
|
return ((), postgrest LogQuiet refConf refDbStructure pool getTime $ pure ())
|
||||||
|
|
||||||
-- For tests that run with a different DbStructure(depends on configSchemas)
|
-- For tests that run with a different DbStructure(depends on configSchemas)
|
||||||
appDbs cfg = do
|
appDbs cfg = do
|
||||||
dbs <- (newIORef . Just) =<< setupDbStructure pool (configSchemas $ cfg testDbConn) actualPgVersion
|
dbs <- (newIORef . Just) =<< setupDbStructure pool (configSchemas $ cfg testDbConn) actualPgVersion
|
||||||
return ((), postgrest LogQuiet (cfg testDbConn) dbs pool getTime $ pure ())
|
refConf <- newIORef $ cfg testDbConn
|
||||||
|
return ((), postgrest LogQuiet refConf dbs pool getTime $ pure ())
|
||||||
|
|
||||||
let withApp = app testCfg
|
let withApp = app testCfg
|
||||||
maxRowsApp = app testMaxRowsCfg
|
maxRowsApp = app testMaxRowsCfg
|
||||||
|
|||||||
@@ -56,6 +56,12 @@ authorsStatus(){
|
|||||||
"http://localhost:$pgrPort/authors_only"
|
"http://localhost:$pgrPort/authors_only"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v1SchemaParentsStatus(){
|
||||||
|
curl -s -o /dev/null -w '%{http_code}' \
|
||||||
|
-H "Accept-Profile: v1" \
|
||||||
|
"http://localhost:$pgrPort/parents"
|
||||||
|
}
|
||||||
|
|
||||||
# Unit Test Templates
|
# Unit Test Templates
|
||||||
readSecretFromFile(){
|
readSecretFromFile(){
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -186,6 +192,86 @@ ensureAppSettings(){
|
|||||||
pgrStop
|
pgrStop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkAppSettingsReload(){
|
||||||
|
pgrStart "./configs/sigusr2-settings.config"
|
||||||
|
while pgrStarted && test "$( rootStatus )" -ne 200
|
||||||
|
do
|
||||||
|
# wait for the server to start
|
||||||
|
sleep 0.1 \
|
||||||
|
|| sleep 1 # fallback: subsecond sleep is not standard and may fail
|
||||||
|
done
|
||||||
|
# change setting
|
||||||
|
replaceConfigValue "app.settings.name_var" "Jane" ./configs/sigusr2-settings.config
|
||||||
|
# reload
|
||||||
|
kill -s SIGUSR2 $pgrPID
|
||||||
|
response=$(curl -s "http://localhost:$pgrPort/rpc/get_guc_value?name=app.settings.name_var")
|
||||||
|
if test "$response" = "\"Jane\""
|
||||||
|
then
|
||||||
|
ok "app.settings.name_var config reloaded with SIGUSR2"
|
||||||
|
else
|
||||||
|
ko "app.settings.name_var config not reloaded with SIGUSR2. Got: $response"
|
||||||
|
fi
|
||||||
|
pgrStop
|
||||||
|
# go back to original setting
|
||||||
|
replaceConfigValue "app.settings.name_var" "John" ./configs/sigusr2-settings.config
|
||||||
|
}
|
||||||
|
|
||||||
|
checkJwtSecretReload(){
|
||||||
|
pgrStart "./configs/sigusr2-settings.config"
|
||||||
|
while pgrStarted && test "$( rootStatus )" -ne 200
|
||||||
|
do
|
||||||
|
# wait for the server to start
|
||||||
|
sleep 0.1 \
|
||||||
|
|| sleep 1 # fallback: subsecond sleep is not standard and may fail
|
||||||
|
done
|
||||||
|
secret="reallyreallyreallyreallyverysafe"
|
||||||
|
# change setting
|
||||||
|
replaceConfigValue "jwt-secret" "$secret" ./configs/sigusr2-settings.config
|
||||||
|
# reload
|
||||||
|
kill -s SIGUSR2 $pgrPID
|
||||||
|
payload='{"role":"postgrest_test_author"}'
|
||||||
|
authorsJwt=$(psql -qtAX "$POSTGREST_TEST_CONNECTION" -c "select jwt.sign('$payload', '$secret');")
|
||||||
|
httpStatus="$( authorsStatus "$authorsJwt" )"
|
||||||
|
if test "$httpStatus" -eq 200
|
||||||
|
then
|
||||||
|
ok "jwt-secret config reloaded with SIGUSR2"
|
||||||
|
else
|
||||||
|
ko "jwt-secret config not reloaded with SIGUSR2. Got: $httpStatus"
|
||||||
|
fi
|
||||||
|
pgrStop
|
||||||
|
# go back to original setting
|
||||||
|
replaceConfigValue "jwt-secret" "invalidinvalidinvalidinvalidinvalid" ./configs/sigusr2-settings.config
|
||||||
|
}
|
||||||
|
|
||||||
|
checkDbSchemaReload(){
|
||||||
|
pgrStart "./configs/sigusr2-settings.config"
|
||||||
|
while pgrStarted && test "$( rootStatus )" -ne 200
|
||||||
|
do
|
||||||
|
# wait for the server to start
|
||||||
|
sleep 0.1 \
|
||||||
|
|| sleep 1 # fallback: subsecond sleep is not standard and may fail
|
||||||
|
done
|
||||||
|
secret="reallyreallyreallyreallyverysafe"
|
||||||
|
# add v1 schema to db-schema
|
||||||
|
replaceConfigValue "db-schema" "test, v1" ./configs/sigusr2-settings.config
|
||||||
|
# reload
|
||||||
|
kill -s SIGUSR2 $pgrPID
|
||||||
|
httpStatus="$(v1SchemaParentsStatus)"
|
||||||
|
if test "$httpStatus" -eq 200
|
||||||
|
then
|
||||||
|
ok "db-schema config reloaded with SIGUSR2"
|
||||||
|
else
|
||||||
|
ko "db-schema config not reloaded with SIGUSR2. Got: $httpStatus"
|
||||||
|
fi
|
||||||
|
pgrStop
|
||||||
|
# go back to original setting
|
||||||
|
replaceConfigValue "db-schema" "test" ./configs/sigusr2-settings.config
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceConfigValue(){
|
||||||
|
sed -i "s/.*$1.*/$1 = \"$2\"/g" $3
|
||||||
|
}
|
||||||
|
|
||||||
getSocketStatus() {
|
getSocketStatus() {
|
||||||
curl -sL -w "%{http_code}\\n" -o /dev/null localhost:54321
|
curl -sL -w "%{http_code}\\n" -o /dev/null localhost:54321
|
||||||
}
|
}
|
||||||
@@ -255,6 +341,11 @@ invalidRoleClaimKey 1234
|
|||||||
ensureIatClaimWorks
|
ensureIatClaimWorks
|
||||||
ensureAppSettings
|
ensureAppSettings
|
||||||
|
|
||||||
|
checkAppSettingsReload
|
||||||
|
checkJwtSecretReload
|
||||||
|
checkDbSchemaReload
|
||||||
|
# TODO: SIGUSR2 tests for other config options
|
||||||
|
|
||||||
trap - int term exit
|
trap - int term exit
|
||||||
|
|
||||||
exit $failedTests
|
exit $failedTests
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
db-uri = "$(POSTGREST_TEST_CONNECTION)"
|
||||||
|
db-schema = "test"
|
||||||
|
db-anon-role = "postgrest_test_anonymous"
|
||||||
|
db-pool = 1
|
||||||
|
server-host = "127.0.0.1"
|
||||||
|
server-port = 49421
|
||||||
|
|
||||||
|
app.settings.name_var = "John"
|
||||||
|
jwt-secret = "invalidinvalidinvalidinvalidinvalid"
|
||||||
Reference in New Issue
Block a user