added: cli option --dump-config prints loaded config and exits
This is most useful for automated tests for upcoming configuration features. Can also be used for debugging.
This commit is contained in:
committed by
Wolfgang Walther
parent
d218a9eaff
commit
eb46cf2662
+9
-5
@@ -27,8 +27,9 @@ import Network.Wai.Handler.Warp (defaultSettings, runSettings,
|
||||
import System.IO (BufferMode (..), hSetBuffering)
|
||||
|
||||
import PostgREST.App (postgrest)
|
||||
import PostgREST.Config (AppConfig (..), configPoolTimeout',
|
||||
prettyVersion, readPathShowHelp,
|
||||
import PostgREST.Config (AppConfig (..), CLI (..), Command (..),
|
||||
configPoolTimeout', dumpAppConfig,
|
||||
prettyVersion, readCLIShowHelp,
|
||||
readValidateConfig)
|
||||
import PostgREST.DbStructure (getDbStructure, getPgVersion)
|
||||
import PostgREST.Error (PgError (PgError), checkIsFatal,
|
||||
@@ -57,10 +58,13 @@ main = do
|
||||
hSetBuffering stderr NoBuffering
|
||||
|
||||
-- read path from commad line
|
||||
path <- readPathShowHelp
|
||||
opts <- readCLIShowHelp
|
||||
|
||||
-- build the 'AppConfig' from the config file path
|
||||
conf <- readValidateConfig path
|
||||
conf <- readValidateConfig $ cliPath opts
|
||||
|
||||
-- dump config and exit if option is set
|
||||
when (cliCommand opts == CmdDumpConfig) $ dumpAppConfig conf
|
||||
|
||||
-- These are config values that can't be reloaded at runtime. Reloading some of them would imply restarting the web server.
|
||||
let
|
||||
@@ -122,7 +126,7 @@ main = do
|
||||
|
||||
-- Re-read the config on SIGUSR2
|
||||
void $ installHandler sigUSR2 (
|
||||
Catch $ reReadConfig path refConf
|
||||
Catch $ reReadConfig (cliPath opts) refConf
|
||||
) Nothing
|
||||
#endif
|
||||
|
||||
|
||||
+97
-25
@@ -19,9 +19,12 @@ Other hardcoded options such as the minimum version number also belong here.
|
||||
|
||||
module PostgREST.Config ( prettyVersion
|
||||
, docsVersion
|
||||
, CLI (..)
|
||||
, Command (..)
|
||||
, AppConfig (..)
|
||||
, configPoolTimeout'
|
||||
, readPathShowHelp
|
||||
, dumpAppConfig
|
||||
, readCLIShowHelp
|
||||
, readValidateConfig
|
||||
)
|
||||
where
|
||||
@@ -30,24 +33,25 @@ import qualified Data.ByteString as B
|
||||
import qualified Data.ByteString.Base64 as B64
|
||||
import qualified Data.ByteString.Char8 as BS
|
||||
import qualified Data.Configurator as C
|
||||
import Data.Either.Combinators (whenLeft)
|
||||
import qualified Text.PrettyPrint.ANSI.Leijen as L
|
||||
|
||||
import Control.Lens (preview)
|
||||
import Control.Monad (fail)
|
||||
import Crypto.JWT (JWKSet, StringOrURI, stringOrUri)
|
||||
import Data.List.NonEmpty (fromList)
|
||||
import Data.Scientific (floatingOrInteger)
|
||||
import Data.Text (dropEnd, dropWhileEnd, intercalate, pack,
|
||||
replace, splitOn, strip, stripPrefix, take,
|
||||
unpack)
|
||||
import Data.Text.IO (hPutStrLn)
|
||||
import Data.Version (versionBranch)
|
||||
import Development.GitRev (gitHash)
|
||||
import Numeric (readOct)
|
||||
import Paths_postgrest (version)
|
||||
import System.IO.Error (IOError)
|
||||
import System.Posix.Types (FileMode)
|
||||
import Control.Lens (preview)
|
||||
import Control.Monad (fail)
|
||||
import Crypto.JWT (JWKSet, StringOrURI, stringOrUri)
|
||||
import Data.Aeson (encode, toJSON)
|
||||
import Data.Either.Combinators (fromRight', whenLeft)
|
||||
import Data.List.NonEmpty (fromList, toList)
|
||||
import Data.Scientific (floatingOrInteger)
|
||||
import Data.Text (dropEnd, dropWhileEnd, intercalate,
|
||||
pack, replace, splitOn, strip,
|
||||
stripPrefix, take, toLower, unpack)
|
||||
import Data.Text.IO (hPutStrLn)
|
||||
import Data.Version (versionBranch)
|
||||
import Development.GitRev (gitHash)
|
||||
import Numeric (readOct, showOct)
|
||||
import Paths_postgrest (version)
|
||||
import System.IO.Error (IOError)
|
||||
import System.Posix.Types (FileMode)
|
||||
|
||||
import Control.Applicative
|
||||
import Data.Monoid
|
||||
@@ -62,9 +66,15 @@ import PostgREST.Types (JSPath, JSPathExp (..),
|
||||
LogLevel (..))
|
||||
import Protolude hiding (concat, hPutStrLn,
|
||||
intercalate, null, replace, take,
|
||||
toS, (<>))
|
||||
toList, toLower, toS, (<>))
|
||||
import Protolude.Conv (toS)
|
||||
|
||||
-- | Command line interface options
|
||||
data CLI = CLI
|
||||
{ cliCommand :: Command
|
||||
, cliPath :: FilePath }
|
||||
|
||||
data Command = CmdRun | CmdDumpConfig deriving (Eq)
|
||||
|
||||
-- | Config file settings for the server
|
||||
data AppConfig = AppConfig {
|
||||
@@ -103,6 +113,7 @@ data AppConfig = AppConfig {
|
||||
|
||||
, configDbPrepared :: Bool
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
configPoolTimeout' :: (Fractional a) => AppConfig -> a
|
||||
configPoolTimeout' =
|
||||
@@ -122,13 +133,13 @@ prettyVersion =
|
||||
docsVersion :: Text
|
||||
docsVersion = "v" <> dropEnd 1 (dropWhileEnd (/= '.') prettyVersion)
|
||||
|
||||
-- | Read config the file path from the command line. Also prints help.
|
||||
readPathShowHelp :: IO FilePath
|
||||
readPathShowHelp = customExecParser parserPrefs opts
|
||||
-- | Read command line interface options. Also prints help.
|
||||
readCLIShowHelp :: IO CLI
|
||||
readCLIShowHelp = customExecParser parserPrefs opts
|
||||
where
|
||||
parserPrefs = prefs showHelpOnError
|
||||
|
||||
opts = info (helper <*> pathParser) $
|
||||
opts = info (helper <*> cliParser) $
|
||||
fullDesc
|
||||
<> progDesc (
|
||||
"PostgREST "
|
||||
@@ -140,11 +151,16 @@ readPathShowHelp = customExecParser parserPrefs opts
|
||||
L.<> nest 2 (hardline L.<> exampleCfg)
|
||||
)
|
||||
|
||||
pathParser :: Parser FilePath
|
||||
pathParser =
|
||||
strArgument $
|
||||
cliParser :: Parser CLI
|
||||
cliParser = CLI <$>
|
||||
flag CmdRun CmdDumpConfig (
|
||||
long "dump-config" <>
|
||||
help "Dump loaded configuration and exit"
|
||||
) <*>
|
||||
strArgument (
|
||||
metavar "FILENAME" <>
|
||||
help "Path to configuration file"
|
||||
)
|
||||
|
||||
exampleCfg :: Doc
|
||||
exampleCfg = vsep . map (text . toS) . lines $
|
||||
@@ -225,6 +241,62 @@ readPathShowHelp = customExecParser parserPrefs opts
|
||||
|log-level = "error"
|
||||
|]
|
||||
|
||||
-- | Dump the config
|
||||
dumpAppConfig :: AppConfig -> IO ()
|
||||
dumpAppConfig conf = do
|
||||
putStr dump
|
||||
exitSuccess
|
||||
|
||||
where
|
||||
dump = unlines $ (\(k, v) -> k <> " = " <> v) <$>
|
||||
pgrstSettings ++ appSettings
|
||||
|
||||
-- apply conf to all pgrst settings
|
||||
pgrstSettings = (\(k, v) -> (k, v conf)) <$>
|
||||
[("db-uri", q . configDbUri)
|
||||
,("db-schema", q . intercalate "," . toList . configSchemas)
|
||||
,("db-anon-role", q . configAnonRole)
|
||||
,("db-pool", show . configPoolSize)
|
||||
,("db-pool-timeout", show . configPoolTimeout)
|
||||
,("db-extra-search-path", q . intercalate "," . configExtraSearchPath)
|
||||
,("db-channel", q . configDbChannel)
|
||||
,("db-channel-enabled", toLower . show . configDbChannelEnabled)
|
||||
,("db-tx-end", q . showTxEnd)
|
||||
,("db-prepared-statements", toLower . show . configDbPrepared)
|
||||
,("server-host", q . configHost)
|
||||
,("server-port", show . configPort)
|
||||
,("server-unix-socket", q . maybe mempty pack . configSocket)
|
||||
,("server-unix-socket-mode", q . pack . showSocketMode)
|
||||
,("openapi-server-proxy-uri", q . fromMaybe mempty . configOpenAPIProxyUri)
|
||||
,("jwt-secret", q . toS . showJwtSecret)
|
||||
,("jwt-aud", toS . encode . maybe "" toJSON . configJwtAudience)
|
||||
,("secret-is-base64", toLower . show . configJwtSecretIsBase64)
|
||||
,("role-claim-key", q . intercalate mempty . fmap show . fromRight' . configRoleClaimKey)
|
||||
,("max-rows", maybe "\"\"" show . configMaxRows)
|
||||
,("pre-request", q . fromMaybe mempty . configPreReq)
|
||||
,("root-spec", q . fromMaybe mempty . configRootSpec)
|
||||
,("raw-media-types", q . toS . B.intercalate "," . configRawMediaTypes)
|
||||
,("log-level", q . show . configLogLevel)
|
||||
]
|
||||
|
||||
-- quote all app.settings
|
||||
appSettings = second q <$> configSettings conf
|
||||
|
||||
-- quote strings and replace " with \"
|
||||
q s = "\"" <> replace "\"" "\\\"" s <> "\""
|
||||
|
||||
showTxEnd c = case (configTxRollbackAll c, configTxAllowOverride c) of
|
||||
( False, False ) -> "commit"
|
||||
( False, True ) -> "commit-allow-override"
|
||||
( True , False ) -> "rollback"
|
||||
( True , True ) -> "rollback-allow-override"
|
||||
showSocketMode c = showOct (fromRight' $ configSocketMode c) ""
|
||||
showJwtSecret c
|
||||
| configJwtSecretIsBase64 c = B64.encode secret
|
||||
| otherwise = toS secret
|
||||
where
|
||||
secret = fromMaybe mempty $ configJwtSecret c
|
||||
|
||||
-- | Parse the config file
|
||||
readAppConfig :: FilePath -> IO AppConfig
|
||||
readAppConfig cfgPath = do
|
||||
|
||||
+13
-2
@@ -546,7 +546,12 @@ sourceCTEName = "pgrst_source"
|
||||
-- | full jspath, e.g. .property[0].attr.detail
|
||||
type JSPath = [JSPathExp]
|
||||
-- | jspath expression, e.g. .property, .property[0] or ."property-dash"
|
||||
data JSPathExp = JSPKey Text | JSPIdx Int deriving (Eq, Show)
|
||||
data JSPathExp = JSPKey Text | JSPIdx Int deriving (Eq)
|
||||
|
||||
instance Show JSPathExp where
|
||||
-- TODO: this needs to be quoted properly for special chars
|
||||
show (JSPKey k) = "." <> show k
|
||||
show (JSPIdx i) = "[" <> show i <> "]"
|
||||
|
||||
-- | Current database connection status data ConnectionStatus
|
||||
data ConnectionStatus
|
||||
@@ -555,4 +560,10 @@ data ConnectionStatus
|
||||
| FatalConnectionError Text
|
||||
deriving (Eq, Show)
|
||||
|
||||
data LogLevel = LogCrit | LogError | LogWarn | LogInfo deriving (Eq, Show)
|
||||
data LogLevel = LogCrit | LogError | LogWarn | LogInfo deriving (Eq)
|
||||
|
||||
instance Show LogLevel where
|
||||
show LogCrit = "crit"
|
||||
show LogError = "error"
|
||||
show LogWarn = "warn"
|
||||
show LogInfo = "info"
|
||||
|
||||
@@ -47,6 +47,54 @@ ok(){ result 'ok' "- $1"; }
|
||||
ko(){ result 'not ok' "- $1"; failedTests=$(( $failedTests + 1 )); }
|
||||
comment(){ echo "# $1"; }
|
||||
|
||||
########################
|
||||
# SYNCHRONOUS IO TESTS #
|
||||
########################
|
||||
|
||||
dumpedConfigMatchesExpectation(){
|
||||
# This test compares the dumped config vs. the corresponding file in ./configs/expected.
|
||||
# To be used to test default values, config aliases and environment variables.
|
||||
dump="$(mktemp)"
|
||||
tap(){
|
||||
if test $1 -eq 0; then
|
||||
ok "dump of config file $2 does match expectation"
|
||||
else
|
||||
ko "dump of config file $2 does not match expectation"
|
||||
fi
|
||||
rm -f "$dump"
|
||||
}
|
||||
trap 'tap $? $1; trap - RETURN; return 0' ERR RETURN
|
||||
postgrest --dump-config "$1" > "$dump"
|
||||
diff "$dump" "$2"
|
||||
}
|
||||
|
||||
dumpedConfigIsValid(){
|
||||
# This test compares the dumped config vs. the dumped-reread-redumped config.
|
||||
# Re-reading the dumped config tests the validity of the config format.
|
||||
# Re-dumping this config should yield no difference to the first dump, showing
|
||||
# that the semantics have not changed by dumping.
|
||||
# Note: only dump vs redump must be equal, the original config file can be different,
|
||||
# because of default values, whitespace, and quoting
|
||||
dump="$(mktemp)"
|
||||
redump="$(mktemp)"
|
||||
tap(){
|
||||
if test $1 -eq 0; then
|
||||
ok "dump of config file $2 is valid"
|
||||
else
|
||||
ko "dump of config file $2 is invalid"
|
||||
fi
|
||||
rm -f "$dump" "$redump"
|
||||
}
|
||||
trap 'tap $? $1; trap - RETURN; return 0' ERR RETURN
|
||||
postgrest --dump-config "$1" > "$dump"
|
||||
postgrest --dump-config "$dump" > "$redump"
|
||||
diff "$dump" "$redump"
|
||||
}
|
||||
|
||||
####################
|
||||
# BACKGROUND TESTS #
|
||||
####################
|
||||
|
||||
# Utilities to start/stop test PostgREST server running in the background
|
||||
pgrStart(){
|
||||
# stderr is not piped to /dev/null to catch errors on startup.
|
||||
@@ -309,6 +357,23 @@ psql -l "$POSTGREST_TEST_CONNECTION" 1>/dev/null 2>/dev/null || bailOut 'postgre
|
||||
|
||||
echo "Running IO tests.."
|
||||
|
||||
# run dumpConfigIsValid with as many inputs as possible
|
||||
for cfg in configs/*.config
|
||||
do
|
||||
# ROLE_CLAIM_KEY is only used in one of the config files
|
||||
# using a complex example here, to make sure the quoting works
|
||||
ROLE_CLAIM_KEY='."https://www.example.com/roles"[0].value' \
|
||||
dumpedConfigIsValid "$cfg" \
|
||||
<<< "Y29ubmVjdGlvbl9zdHJpbmc=" # /dev/stdin is read by some config files, one of them expects Base64
|
||||
done
|
||||
|
||||
# run dumpConfigMatchesExpectation with all expectations
|
||||
for exp in configs/expected/*.config
|
||||
do
|
||||
cfg="$(sed -e 's|expected/||' <(echo $exp))"
|
||||
dumpedConfigMatchesExpectation "$cfg" "$exp"
|
||||
done
|
||||
|
||||
socketConnection
|
||||
|
||||
readSecretFromFile word.noeol 'simple (no EOL)'
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
db-uri = "required"
|
||||
db-schema = "required"
|
||||
db-anon-role = "required"
|
||||
@@ -0,0 +1,24 @@
|
||||
db-uri = "required"
|
||||
db-schema = "required"
|
||||
db-anon-role = "required"
|
||||
db-pool = 10
|
||||
db-pool-timeout = 10
|
||||
db-extra-search-path = "public"
|
||||
db-channel = "pgrst"
|
||||
db-channel-enabled = false
|
||||
db-tx-end = "commit"
|
||||
db-prepared-statements = true
|
||||
server-host = "!4"
|
||||
server-port = 3000
|
||||
server-unix-socket = ""
|
||||
server-unix-socket-mode = "660"
|
||||
openapi-server-proxy-uri = ""
|
||||
jwt-secret = ""
|
||||
jwt-aud = ""
|
||||
secret-is-base64 = false
|
||||
role-claim-key = ".\"role\""
|
||||
max-rows = ""
|
||||
pre-request = ""
|
||||
root-spec = ""
|
||||
raw-media-types = ""
|
||||
log-level = "error"
|
||||
@@ -0,0 +1,26 @@
|
||||
db-uri = "tmp_db"
|
||||
db-schema = "multi,tenant,setup"
|
||||
db-anon-role = "root"
|
||||
db-pool = 1
|
||||
db-pool-timeout = 100
|
||||
db-extra-search-path = "public,test"
|
||||
db-channel = "postgrest"
|
||||
db-channel-enabled = true
|
||||
db-tx-end = "rollback-allow-override"
|
||||
db-prepared-statements = false
|
||||
server-host = "0.0.0.0"
|
||||
server-port = 80
|
||||
server-unix-socket = "/tmp/pgrst_io_test.sock"
|
||||
server-unix-socket-mode = "777"
|
||||
openapi-server-proxy-uri = "https://postgrest.org"
|
||||
jwt-secret = "c2VjdXJpdHl0aHJvdWdob2JzY3VyaXR5"
|
||||
jwt-aud = "https://postgrest.org"
|
||||
secret-is-base64 = true
|
||||
role-claim-key = ".\"user\"[0].\"real-role\""
|
||||
max-rows = 1000
|
||||
pre-request = "please_run_fast"
|
||||
root-spec = "openapi_v3"
|
||||
raw-media-types = "application/vnd.pgrst.config"
|
||||
log-level = "info"
|
||||
app.settings.test = "test"
|
||||
app.settings.test2 = "test"
|
||||
@@ -0,0 +1,26 @@
|
||||
db-uri = "tmp_db"
|
||||
db-schema = "multi, tenant,setup"
|
||||
db-anon-role = "root"
|
||||
db-pool = 1
|
||||
db-pool-timeout = 100
|
||||
db-extra-search-path = "public, test"
|
||||
db-channel = "postgrest"
|
||||
db-channel-enabled = true
|
||||
db-tx-end = "rollback-allow-override"
|
||||
db-prepared-statements = false
|
||||
server-host = "0.0.0.0"
|
||||
server-port = 80
|
||||
server-unix-socket = "/tmp/pgrst_io_test.sock"
|
||||
server-unix-socket-mode = "777"
|
||||
openapi-server-proxy-uri = "https://postgrest.org"
|
||||
jwt-secret = "c2VjdXJpdHl0aHJvdWdob2JzY3VyaXR5"
|
||||
jwt-aud = "https://postgrest.org"
|
||||
secret-is-base64 = true
|
||||
role-claim-key = ".user[0].\"real-role\""
|
||||
max-rows = 1000
|
||||
pre-request = "please_run_fast"
|
||||
root-spec = "openapi_v3"
|
||||
raw-media-types = "application/vnd.pgrst.config"
|
||||
log-level = "info"
|
||||
app.settings.test = "test"
|
||||
app.settings.test2 = "test"
|
||||
Reference in New Issue
Block a user