Read settings from config file (#714)

This commit is contained in:
Joe Nelson
2016-10-20 08:34:57 -07:00
committed by GitHub
parent 74e68408e6
commit 9233d90075
6 changed files with 121 additions and 46 deletions
+1
View File
@@ -32,6 +32,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- HTTP 401 rather than 400 for expired JWT - @begriffs - HTTP 401 rather than 400 for expired JWT - @begriffs
- Remove default JWT secret - @begriffs - Remove default JWT secret - @begriffs
- Use GUC request.jwt.claim.foo rather than postgrest.claims.foo - @begriffs - Use GUC request.jwt.claim.foo rather than postgrest.claims.foo - @begriffs
- Use config file rather than command line arguments - @begriffs
## [0.3.2.0] - 2016-06-10 ## [0.3.2.0] - 2016-06-10
+4 -13
View File
@@ -1,11 +1,6 @@
FROM debian:jessie FROM debian:jessie
ENV POSTGREST_VERSION 0.3.2.0 ENV POSTGREST_VERSION 0.4.0.0
ENV POSTGREST_SCHEMA public
ENV POSTGREST_ANONYMOUS postgres
ENV POSTGREST_JWT_SECRET thisisnotarealsecret
ENV POSTGREST_MAX_ROWS 1000000
ENV POSTGREST_POOL 200
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y tar xz-utils wget libpq-dev && \ apt-get install -y tar xz-utils wget libpq-dev && \
@@ -16,12 +11,8 @@ RUN wget http://github.com/begriffs/postgrest/releases/download/v${POSTGREST_VER
mv postgrest /usr/local/bin/postgrest && \ mv postgrest /usr/local/bin/postgrest && \
rm postgrest-${POSTGREST_VERSION}-ubuntu.tar.xz rm postgrest-${POSTGREST_VERSION}-ubuntu.tar.xz
CMD exec postgrest postgres://${PG_ENV_POSTGRES_USER}:${PG_ENV_POSTGRES_PASSWORD}@${PG_PORT_5432_TCP_ADDR}:${PG_PORT_5432_TCP_PORT}/${PG_ENV_POSTGRES_DB} \ # PostgREST reads /etc/postgrest.conf so map the configuration
--port 3000 \ # file in when you run this container
--schema ${POSTGREST_SCHEMA} \ CMD exec postgrest
--anonymous ${POSTGREST_ANONYMOUS} \
--pool ${POSTGREST_POOL} \
--jwt-secret ${POSTGREST_JWT_SECRET} \
--max-rows ${POSTGREST_MAX_ROWS}
EXPOSE 3000 EXPOSE 3000
+6
View File
@@ -8,12 +8,14 @@ import PostgREST.Config (AppConfig (..),
minimumPgVersion, minimumPgVersion,
prettyVersion, prettyVersion,
readOptions) readOptions)
import PostgREST.Error (prettyUsageError)
import PostgREST.OpenAPI (isMalformedProxyUri) import PostgREST.OpenAPI (isMalformedProxyUri)
import PostgREST.DbStructure import PostgREST.DbStructure
import Control.AutoUpdate import Control.AutoUpdate
import Data.String (IsString (..)) import Data.String (IsString (..))
import Data.Text (stripPrefix) import Data.Text (stripPrefix)
import Data.Text.IO (hPutStrLn)
import Data.Function (id) import Data.Function (id)
import Data.Time.Clock.POSIX (getPOSIXTime) import Data.Time.Clock.POSIX (getPOSIXTime)
import qualified Hasql.Query as H import qualified Hasql.Query as H
@@ -68,6 +70,10 @@ main = do
<> show minimumPgVersion) <> show minimumPgVersion)
getDbStructure (toS $ configSchema conf) getDbStructure (toS $ configSchema conf)
forM_ (lefts [result]) $ \e -> do
hPutStrLn stderr (prettyUsageError e)
exitFailure
refDbStructure <- newIORef $ either (panic . show) id result refDbStructure <- newIORef $ either (panic . show) id result
#ifndef mingw32_HOST_OS #ifndef mingw32_HOST_OS
+2
View File
@@ -51,11 +51,13 @@ library
, bytestring , bytestring
, case-insensitive , case-insensitive
, cassava , cassava
, configurator
, containers , containers
, contravariant , contravariant
, hasql , hasql
, hasql-pool == 0.4.1 , hasql-pool == 0.4.1
, hasql-transaction == 0.4.5.1 , hasql-transaction == 0.4.5.1
, heredoc
, HTTP , HTTP
, http-types , http-types
, insert-ordered-containers , insert-ordered-containers
+102 -32
View File
@@ -2,12 +2,13 @@
Module : PostgREST.Config Module : PostgREST.Config
Description : Manages PostgREST configuration options. Description : Manages PostgREST configuration options.
This module provides a helper function to read the command line arguments using the optparse-applicative This module provides a helper function to read the command line
and the AppConfig type to store them. arguments using the optparse-applicative and the AppConfig type to store
It also can be used to define other middleware configuration that may be delegated to some sort of them. It also can be used to define other middleware configuration that
external configuration. 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. 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. Other hardcoded options such as the minimum version number also belong here.
-} -}
@@ -19,20 +20,26 @@ module PostgREST.Config ( prettyVersion
) )
where where
import System.IO.Error (IOError)
import Control.Applicative
import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Char8 as BS
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
import qualified Data.Configurator as C
import qualified Data.Configurator.Types as C
import Data.List (lookup) import Data.List (lookup)
import Data.Text (strip, intercalate) import Data.Text (strip, intercalate)
import Data.Text.IO (hPutStrLn)
import Data.Version (versionBranch) import Data.Version (versionBranch)
import Network.Wai import Network.Wai
import Network.Wai.Middleware.Cors (CorsResourcePolicy (..)) import Network.Wai.Middleware.Cors (CorsResourcePolicy (..))
import Options.Applicative import Options.Applicative hiding (str)
import Paths_postgrest (version) import Paths_postgrest (version)
import Text.Heredoc
import Protolude hiding (intercalate import Protolude hiding (intercalate
, (<>)) , (<>))
import Safe (readMay)
-- | Data type to store all command line options -- | Config file settings for the server
data AppConfig = AppConfig { data AppConfig = AppConfig {
configDatabase :: Text configDatabase :: Text
, configAnonRole :: Text , configAnonRole :: Text
@@ -47,20 +54,6 @@ data AppConfig = AppConfig {
, configQuiet :: Bool , configQuiet :: Bool
} }
argParser :: Parser AppConfig
argParser = AppConfig
<$> (toS <$> argument str (help "(REQUIRED) database connection string, e.g. postgres://user:pass@host:port/db" <> metavar "DB_URL"))
<*> (toS <$> strOption (long "anonymous" <> short 'a' <> help "(REQUIRED) postgres role to use for non-authenticated requests" <> metavar "ROLE"))
<*> (optional . map toS . strOption) (long "proxy-uri" <> short 'x' <> help "proxy uri of the HTTP server" <> metavar "PROXY")
<*> (toS <$> strOption (long "schema" <> short 's' <> help "schema to use for API routes" <> metavar "NAME" <> value "public" <> showDefault))
<*> (toS <$> strOption (long "host" <> short 'l' <> help "hostname or ip on which to run HTTP server" <> metavar "HOST" <> value "*4" <> showDefault))
<*> option auto (long "port" <> short 'p' <> help "port number on which to run HTTP server" <> metavar "PORT" <> value 3000 <> showDefault)
<*> (optional . map toS <$> strOption) (long "jwt-secret" <> short 'j' <> help "secret used to encrypt and decrypt JWT tokens" <> metavar "SECRET")
<*> option auto (long "pool" <> short 'o' <> help "max connections in database pool" <> metavar "COUNT" <> value 10 <> showDefault)
<*> (readMay <$> strOption (long "max-rows" <> short 'm' <> help "max rows in response" <> metavar "COUNT" <> value "infinity" <> showDefault))
<*> (optional . map toS . strOption) (long "pre-request" <> help "schema-qualified name of proc to call to validate requests" <> metavar "FUNCTION")
<*> pure False
defaultCorsPolicy :: CorsResourcePolicy defaultCorsPolicy :: CorsResourcePolicy
defaultCorsPolicy = CorsResourcePolicy Nothing defaultCorsPolicy = CorsResourcePolicy Nothing
["GET", "POST", "PATCH", "DELETE", "OPTIONS"] ["Authorization"] Nothing ["GET", "POST", "PATCH", "DELETE", "OPTIONS"] ["Authorization"] Nothing
@@ -90,16 +83,93 @@ prettyVersion = intercalate "." $ map show $ versionBranch version
-- | Function to read and parse options from the command line -- | Function to read and parse options from the command line
readOptions :: IO AppConfig readOptions :: IO AppConfig
readOptions = customExecParser parserPrefs opts readOptions = do
where args <- customExecParser parserPrefs opts
opts = info (helper <*> argParser) $
fullDesc when (caExample args) $ do
<> progDesc ( putStrLn (
"PostgREST " [str|db-uri = "postgres://user:pass@localhost:5432/dbname"
<> toS prettyVersion |db-schema = "public"
<> " / create a REST API to an existing Postgres database" |db-anon-role = "postgres"
) |db-pool = 10
parserPrefs = prefs showHelpOnError |
|server-host = "*4"
|server-port = 3000
|
|## base url for swagger output
|# server-proxy-uri = ""
|
|## choose a secret to enable JWT auth
|## (use "@filename" to load from separate file)
|# jwt-secret = "foo"
|
|## limit rows in response
|# max-rows = 1000
|
|## stored proc to exec immediately after auth
|# pre-request = "stored_proc_name"
|]::Text)
exitSuccess
conf <- catch
(C.load [C.Required $ caConfig args])
configNotfoundHint
handle missingKeyHint $ do
-- db ----------------
cDbUri <- C.require conf "db-uri"
cDbSchema <- C.require conf "db-schema"
cDbAnon <- C.require conf "db-anon-role"
cPool <- C.lookupDefault 10 conf "db-pool"
-- server ------------
cHost <- C.lookupDefault "*4" conf "server-host"
cPort <- C.lookupDefault 3000 conf "server-port"
cProxy <- C.lookup conf "server-proxy-uri"
-- jwt ---------------
cJwtSec <- C.lookup conf "jwt-secret"
-- safety ------------
cMaxRows <- C.lookup conf "max-rows"
cReqCheck <- C.lookup conf "pre-request"
return $ AppConfig cDbUri cDbAnon cProxy cDbSchema cHost cPort
cJwtSec cPool cMaxRows cReqCheck False
where
opts = info (helper <*> argParser) $
fullDesc
<> progDesc (
"PostgREST "
<> toS prettyVersion
<> " / create a REST API to an existing Postgres database"
)
parserPrefs = prefs showHelpOnError
configNotfoundHint :: IOError -> IO a
configNotfoundHint e = do
hPutStrLn stderr $ intercalate "\n" [
"Cannot open config file:",
"\t" <> show e,
"\nUse the --help flag to learn how to fix this."]
exitFailure
missingKeyHint :: C.KeyError -> IO a
missingKeyHint (C.KeyError n) = do
hPutStrLn stderr $
"Required config parameter \"" <> n <> "\" is missing or of wrong type.\n" <>
"Try the --example-config option to see how to configure PostgREST."
exitFailure
data CmdArgs = CmdArgs {
caConfig :: FilePath
, caExample :: Bool
}
argParser :: Parser CmdArgs
argParser = CmdArgs <$>
(toS <$> strOption
(short 'c' <> metavar "filename" <>
help "Path to configuration file")) <*>
switch (long "example-config" <> help "output an example config file")
-- | Tells the minimum PostgreSQL version required by this version of PostgREST -- | Tells the minimum PostgreSQL version required by this version of PostgREST
minimumPgVersion :: Integer minimumPgVersion :: Integer
+6 -1
View File
@@ -2,7 +2,7 @@
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE TypeSynonymInstances #-}
module PostgREST.Error (pgErrResponse, errResponse) where module PostgREST.Error (pgErrResponse, errResponse, prettyUsageError) where
import Protolude import Protolude
import Data.Aeson ((.=)) import Data.Aeson ((.=))
@@ -29,6 +29,11 @@ pgErrResponse authed e =
else [jsonType] in else [jsonType] in
responseLBS status hdrs (JSON.encode e) responseLBS status hdrs (JSON.encode e)
prettyUsageError :: P.UsageError -> Text
prettyUsageError (P.ConnectionError e) =
"Database connection error:\n" <> toS (fromMaybe "" e)
prettyUsageError e = show $ JSON.encode e
instance JSON.ToJSON P.UsageError where instance JSON.ToJSON P.UsageError where
toJSON (P.ConnectionError e) = JSON.object [ toJSON (P.ConnectionError e) = JSON.object [
"code" .= ("" :: Text), "code" .= ("" :: Text),