219 lines
7.6 KiB
Haskell
219 lines
7.6 KiB
Haskell
{-# LANGUAGE NamedFieldPuns #-}
|
|
{-# LANGUAGE QuasiQuotes #-}
|
|
{-# LANGUAGE RecordWildCards #-}
|
|
module PostgREST.CLI
|
|
( main
|
|
, CLI (..)
|
|
, Command (..)
|
|
, readCLIShowHelp
|
|
) where
|
|
|
|
import qualified Data.Aeson as JSON
|
|
import qualified Data.ByteString.Char8 as BS
|
|
import qualified Data.ByteString.Lazy as LBS
|
|
import qualified Hasql.Pool as SQL
|
|
import qualified Hasql.Transaction.Sessions as SQL
|
|
import qualified Options.Applicative as O
|
|
|
|
import Data.Text.IO (hPutStrLn)
|
|
import Text.Heredoc (str)
|
|
|
|
import PostgREST.AppState (AppState)
|
|
import PostgREST.Config (AppConfig (..))
|
|
import PostgREST.DbStructure (queryDbStructure)
|
|
import PostgREST.Version (prettyVersion)
|
|
import PostgREST.Workers (reReadConfig)
|
|
|
|
import qualified PostgREST.App as App
|
|
import qualified PostgREST.AppState as AppState
|
|
import qualified PostgREST.Config as Config
|
|
|
|
import Protolude hiding (hPutStrLn)
|
|
|
|
|
|
main :: App.SignalHandlerInstaller -> Maybe App.SocketRunner -> CLI -> IO ()
|
|
main installSignalHandlers runAppWithSocket CLI{cliCommand, cliPath} = do
|
|
conf@AppConfig{..} <-
|
|
either panic identity <$> Config.readAppConfig mempty cliPath Nothing
|
|
appState <- AppState.init conf
|
|
|
|
-- Override the config with config options from the db
|
|
-- TODO: the same operation is repeated on connectionWorker, ideally this
|
|
-- would be done only once, but dump CmdDumpConfig needs it for tests.
|
|
when configDbConfig $ reReadConfig True appState
|
|
|
|
exec cliCommand appState
|
|
where
|
|
exec :: Command -> AppState -> IO ()
|
|
exec CmdDumpConfig appState = putStr . Config.toText =<< AppState.getConfig appState
|
|
exec CmdDumpSchema appState = putStrLn =<< dumpSchema appState
|
|
exec CmdRun appState = App.run installSignalHandlers runAppWithSocket appState
|
|
|
|
-- | Dump DbStructure schema to JSON
|
|
dumpSchema :: AppState -> IO LBS.ByteString
|
|
dumpSchema appState = do
|
|
AppConfig{..} <- AppState.getConfig appState
|
|
actualPgVersion <- AppState.getPgVersion appState
|
|
result <-
|
|
let transaction = if configDbPreparedStatements then SQL.transaction else SQL.unpreparedTransaction in
|
|
SQL.use (AppState.getPool appState) $
|
|
transaction SQL.ReadCommitted SQL.Read $
|
|
queryDbStructure
|
|
(toList configDbSchemas)
|
|
configDbExtraSearchPath
|
|
actualPgVersion
|
|
configDbPreparedStatements
|
|
SQL.release $ AppState.getPool appState
|
|
case result of
|
|
Left e -> do
|
|
hPutStrLn stderr $ "An error ocurred when loading the schema cache:\n" <> show e
|
|
exitFailure
|
|
Right dbStructure -> return $ JSON.encode dbStructure
|
|
|
|
-- | Command line interface options
|
|
data CLI = CLI
|
|
{ cliCommand :: Command
|
|
, cliPath :: Maybe FilePath
|
|
}
|
|
|
|
data Command
|
|
= CmdRun
|
|
| CmdDumpConfig
|
|
| CmdDumpSchema
|
|
|
|
-- | Read command line interface options. Also prints help.
|
|
readCLIShowHelp :: IO CLI
|
|
readCLIShowHelp =
|
|
O.customExecParser prefs opts
|
|
where
|
|
prefs = O.prefs $ O.showHelpOnError <> O.showHelpOnEmpty
|
|
opts = O.info parser $ O.fullDesc <> progDesc
|
|
parser = O.helper <*> exampleParser <*> cliParser
|
|
|
|
progDesc =
|
|
O.progDesc $
|
|
"PostgREST "
|
|
<> BS.unpack prettyVersion
|
|
<> " / create a REST API to an existing Postgres database"
|
|
|
|
exampleParser =
|
|
O.infoOption exampleConfigFile $
|
|
O.long "example"
|
|
<> O.short 'e'
|
|
<> O.help "Show an example configuration file"
|
|
|
|
cliParser :: O.Parser CLI
|
|
cliParser =
|
|
CLI
|
|
<$> (dumpConfigFlag <|> dumpSchemaFlag)
|
|
<*> O.optional configFileOption
|
|
|
|
configFileOption =
|
|
O.strArgument $
|
|
O.metavar "FILENAME"
|
|
<> O.help "Path to configuration file"
|
|
|
|
dumpConfigFlag =
|
|
O.flag CmdRun CmdDumpConfig $
|
|
O.long "dump-config"
|
|
<> O.help "Dump loaded configuration and exit"
|
|
|
|
dumpSchemaFlag =
|
|
O.flag CmdRun CmdDumpSchema $
|
|
O.long "dump-schema"
|
|
<> O.help "Dump loaded schema as JSON and exit (for debugging, output structure is unstable)"
|
|
|
|
exampleConfigFile :: [Char]
|
|
exampleConfigFile =
|
|
[str|## Admin server used for checks. It's disabled by default unless a port is specified.
|
|
|# admin-server-port = 3001
|
|
|
|
|
|## The database role to use when no client authentication is provided
|
|
|# db-anon-role = "anon"
|
|
|
|
|
|## Notification channel for reloading the schema cache
|
|
|db-channel = "pgrst"
|
|
|
|
|
|## Enable or disable the notification channel
|
|
|db-channel-enabled = true
|
|
|
|
|
|## Enable in-database configuration
|
|
|db-config = true
|
|
|
|
|
|## Extra schemas to add to the search_path of every request
|
|
|db-extra-search-path = "public"
|
|
|
|
|
|## Limit rows in response
|
|
|# db-max-rows = 1000
|
|
|
|
|
|## 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
|
|
|
|
|
|## Stored proc to exec immediately after auth
|
|
|# db-pre-request = "stored_proc_name"
|
|
|
|
|
|## 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
|
|
|
|
|
|## The name of which database schema to expose to REST clients
|
|
|db-schemas = "public"
|
|
|
|
|
|## 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"
|
|
|
|
|
|## The standard connection URI format, documented at
|
|
|## https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
|
|
|db-uri = "postgresql://"
|
|
|
|
|
|## Determine if GUC request settings for headers, cookies and jwt claims use the legacy names (string with dashes, invalid starting from PostgreSQL v14) with text values instead of the new names (string without dashes, valid on all PostgreSQL versions) with json values.
|
|
|## For PostgreSQL v14 and up, this setting will be ignored.
|
|
|db-use-legacy-gucs = true
|
|
|
|
|
|# jwt-aud = "your_audience_claim"
|
|
|
|
|
|## Jspath to the role claim key
|
|
|jwt-role-claim-key = ".role"
|
|
|
|
|
|## 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-secret-is-base64 = false
|
|
|
|
|
|## Logging level, the admitted values are: crit, error, warn and info.
|
|
|log-level = "error"
|
|
|
|
|
|## Determine if the OpenAPI output should follow or ignore role privileges or be disabled entirely.
|
|
|## Admitted values: follow-privileges, ignore-privileges, disabled
|
|
|openapi-mode = "follow-privileges"
|
|
|
|
|
|## Base url for the OpenAPI output
|
|
|openapi-server-proxy-uri = ""
|
|
|
|
|
|## Content types to produce raw output
|
|
|# raw-media-types="image/png, image/jpg"
|
|
|
|
|
|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"
|
|
|]
|