refactor: Carve CLI and Version modules out of Config (#1806)

This commit is contained in:
Remo Rechkemmer
2021-04-12 19:17:47 +02:00
committed by GitHub
parent 67ca814d0b
commit 801e229c59
7 changed files with 221 additions and 201 deletions
+9 -194
View File
@@ -1,46 +1,28 @@
{-|
Module : PostgREST.Config
Description : Manages PostgREST configuration options.
Description : Manages PostgREST configuration type and parser.
This module provides a helper function to read the command line
arguments using the optparse-applicative and the AppConfig type to store
them. It also can be used to define other middleware configuration that
may be delegated to some sort of external configuration.
It currently includes a hardcoded CORS policy but this could easly be
turned in configurable behaviour if needed.
Other hardcoded options such as the minimum version number also belong here.
-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -fno-warn-type-defaults #-}
module PostgREST.Config
( prettyVersion
, docsVersion
( AppConfig (..)
, Environment
, JSPath
, JSPathExp(..)
, LogLevel(..)
, CLI (..)
, Command (..)
, AppConfig (..)
, Proxy(..)
, configDbPoolTimeout'
, dumpAppConfig
, Environment
, readCLIShowHelp
, readEnvironment
, isMalformedProxyUri
, parseSecret
, readAppConfig
, readDbUriFile
, readSecretFile
, parseSecret
, JSPath
, JSPathExp(..)
, isMalformedProxyUri
, toURI
) where
@@ -65,18 +47,8 @@ import Data.List (lookup)
import Data.List.NonEmpty (fromList, toList)
import Data.Maybe (fromJust)
import Data.Scientific (floatingOrInteger)
import Data.Version (versionBranch)
import Development.GitRev (gitHash)
import Numeric (readOct, showOct)
import Options.Applicative (Parser, customExecParser, flag,
footer, fullDesc, help, helper, info,
infoOption, long, metavar, prefs,
progDesc, short, showHelpOnEmpty,
showHelpOnError, strArgument)
import Paths_postgrest (version)
import System.Environment (getEnvironment)
import System.Posix.Types (FileMode)
import Text.Heredoc (str)
import PostgREST.Config.JSPath (JSPath, JSPathExp (..), pRoleClaimKey)
import PostgREST.Config.Proxy (Proxy (..), isMalformedProxyUri,
@@ -85,17 +57,7 @@ import PostgREST.Config.Proxy (Proxy (..), isMalformedProxyUri,
import Protolude hiding (Proxy, toList, toS)
import Protolude.Conv (toS)
-- | Command line interface options
data CLI = CLI
{ cliCommand :: Command
, cliPath :: Maybe FilePath }
data Command
= CmdRun
| CmdDumpConfig
| CmdDumpSchema
-- | Config file settings for the server
data AppConfig = AppConfig {
configAppSettings :: [(Text, Text)]
, configDbAnonRole :: Text
@@ -139,148 +101,6 @@ instance Show LogLevel where
show LogWarn = "warn"
show LogInfo = "info"
-- | User friendly version number
prettyVersion :: Text
prettyVersion =
T.intercalate "." (map show $ versionBranch version) <> gitRev
where
gitRev =
if $(gitHash) == "UNKNOWN"
then mempty
else " (" <> T.take 7 $(gitHash) <> ")"
-- | Version number used in docs
docsVersion :: Text
docsVersion = "v" <> T.dropEnd 1 (T.dropWhileEnd (/= '.') prettyVersion)
-- | Read command line interface options. Also prints help.
readCLIShowHelp :: Environment -> IO CLI
readCLIShowHelp env = customExecParser parserPrefs opts
where
parserPrefs = prefs $ showHelpOnError <> showHelpOnEmpty
opts = info (helper <*> exampleParser <*> cliParser) $
fullDesc
<> progDesc (
"PostgREST "
<> toS prettyVersion
<> " / create a REST API to an existing Postgres database"
)
<> footer "To run PostgREST, please pass the FILENAME argument or set PGRST_ environment variables."
cliParser :: Parser CLI
cliParser = CLI <$>
(
flag CmdRun CmdDumpConfig (
long "dump-config" <>
help "Dump loaded configuration and exit"
)
<|>
flag CmdRun CmdDumpSchema (
long "dump-schema" <>
help "Dump loaded schema as JSON and exit (for debugging, output structure is unstable)"
)
)
<*>
optionalWithEnvironment (strArgument (
metavar "FILENAME" <>
help "Path to configuration file (optional with PGRST_ environment variables)"
))
optionalWithEnvironment :: Alternative f => f a -> f (Maybe a)
optionalWithEnvironment v
| M.null env = Just <$> v
| otherwise = optional v
exampleParser :: Parser (a -> a)
exampleParser =
infoOption example (
long "example" <>
short 'e' <>
help "Show an example configuration file"
)
example =
[str|### REQUIRED:
|db-uri = "postgres://user:pass@localhost:5432/dbname"
|db-schema = "public"
|db-anon-role = "postgres"
|
|### OPTIONAL:
|## number of open connections in the pool
|db-pool = 10
|
|## Time to live, in seconds, for an idle database pool connection.
|db-pool-timeout = 10
|
|## extra schemas to add to the search_path of every request
|db-extra-search-path = "public"
|
|## limit rows in response
|# db-max-rows = 1000
|
|## stored proc to exec immediately after auth
|# db-pre-request = "stored_proc_name"
|
|## stored proc that overrides the root "/" spec
|## it must be inside the db-schema
|# db-root-spec = "stored_proc_name"
|
|## Notification channel for reloading the schema cache
|db-channel = "pgrst"
|
|## Enable or disable the notification channel
|db-channel-enabled = false
|
|## Enable in-database configuration
|db-config = true
|
|## how to terminate database transactions
|## possible values are:
|## commit (default)
|## transaction is always committed, this can not be overriden
|## commit-allow-override
|## transaction is committed, but can be overriden with Prefer tx=rollback header
|## rollback
|## transaction is always rolled back, this can not be overriden
|## rollback-allow-override
|## transaction is rolled back, but can be overriden with Prefer tx=commit header
|db-tx-end = "commit"
|
|## enable or disable prepared statements. disabling is only necessary when behind a connection pooler.
|## when disabled, statements will be parametrized but won't be prepared.
|db-prepared-statements = true
|
|server-host = "!4"
|server-port = 3000
|
|## unix socket location
|## if specified it takes precedence over server-port
|# server-unix-socket = "/tmp/pgrst.sock"
|
|## unix socket file mode
|## when none is provided, 660 is applied by default
|# server-unix-socket-mode = "660"
|
|## base url for swagger output
|openapi-server-proxy-uri = ""
|
|## choose a secret, JSON Web Key (or set) to enable JWT auth
|## (use "@filename" to load from separate file)
|# jwt-secret = "secret_with_at_least_32_characters"
|# jwt-aud = "your_audience_claim"
|jwt-secret-is-base64 = false
|
|## jspath to the role claim key
|jwt-role-claim-key = ".role"
|
|## content types to produce raw output
|# raw-media-types="image/png, image/jpg"
|
|## logging level, the admitted values are: crit, error, warn and info.
|log-level = "error"
|]
-- | Dump the config
dumpAppConfig :: AppConfig -> Text
dumpAppConfig conf =
@@ -568,13 +388,6 @@ parseSecret bytes =
secret = JWT.JWKSet [JWT.fromKeyMaterial keyMaterial]
keyMaterial = JWT.OctKeyMaterial . JWT.OctKeyParameters $ JOSE.Base64Octets bytes
type Environment = M.Map [Char] Text
readEnvironment :: IO Environment
readEnvironment = getEnvironment <&> pgrst
where
pgrst env = M.filterWithKey (\k _ -> "PGRST_" `isPrefixOf` k) $ M.map T.pack $ M.fromList env
-- | Read the JWT secret from a file if configJwtSecret is actually a filepath(has @ as its prefix).
-- | To check if the JWT secret is provided is in fact a file path, it must be decoded as 'Text' to be processed.
readSecretFile :: Maybe B.ByteString -> IO (Maybe B.ByteString)
@@ -590,3 +403,5 @@ readDbUriFile :: Text -> IO (Maybe Text)
readDbUriFile dbUri = case T.stripPrefix "@" dbUri of
Nothing -> return Nothing
Just filename -> Just . T.strip <$> readFile (toS filename)
type Environment = M.Map [Char] Text