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:
Wolfgang Walther
2020-12-06 13:33:25 +01:00
committed by Wolfgang Walther
parent d218a9eaff
commit eb46cf2662
8 changed files with 263 additions and 32 deletions
+9 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+65
View File
@@ -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)'
+3
View File
@@ -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"
+26
View File
@@ -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"