diff --git a/main/Main.hs b/main/Main.hs index f3a48bf40..7eda6edec 100644 --- a/main/Main.hs +++ b/main/Main.hs @@ -31,7 +31,7 @@ import System.IO (BufferMode (..), hSetBuffering) import PostgREST.App (postgrest) import PostgREST.Config (AppConfig (..), configPoolTimeout', - prettyVersion, readOptions) + prettyVersion, readAppConfig, readPath) import PostgREST.DbStructure (getDbStructure, getPgVersion) import PostgREST.Error (PgError (PgError), checkIsFatal, errorPayload) @@ -200,10 +200,13 @@ main = do hSetBuffering stdout LineBuffering hSetBuffering stdin LineBuffering hSetBuffering stderr NoBuffering - -- + + path <- readPath + -- readOptions builds the 'AppConfig' from the config file specified on the -- command line - conf <- loadDbUriFile =<< loadSecretFile =<< readOptions + conf <- loadDbUriFile =<< loadSecretFile =<< readAppConfig path + let schemas = toList $ configSchemas conf host = configHost conf port = configPort conf diff --git a/src/PostgREST/Config.hs b/src/PostgREST/Config.hs index f1a9a2c1f..1b2d2ab8b 100644 --- a/src/PostgREST/Config.hs +++ b/src/PostgREST/Config.hs @@ -19,7 +19,8 @@ Other hardcoded options such as the minimum version number also belong here. module PostgREST.Config ( prettyVersion , docsVersion - , readOptions + , readPath + , readAppConfig , corsPolicy , AppConfig (..) , configPoolTimeout' @@ -93,6 +94,8 @@ data AppConfig = AppConfig { , configRootSpec :: Maybe Text , configRawMediaTypes :: [B.ByteString] + + , configPath :: Maybe FilePath } configPoolTimeout' :: (Fractional a) => AppConfig -> a @@ -137,25 +140,100 @@ prettyVersion = docsVersion :: Text docsVersion = "v" <> dropEnd 1 (dropWhileEnd (/= '.') prettyVersion) --- | Function to read and parse options from the command line -readOptions :: IO AppConfig -readOptions = do - -- First read the config file path from command line - cfgPath <- customExecParser parserPrefs opts +-- | Read config the file path from the command line. Also print helpful messages. +readPath :: IO FilePath +readPath = customExecParser parserPrefs opts + where + parserPrefs = prefs showHelpOnError + + opts = info (helper <*> pathParser) $ + fullDesc + <> progDesc ( + "PostgREST " + <> toS prettyVersion + <> " / create a REST API to an existing Postgres database" + ) + <> footerDoc (Just $ + text "Example Config File:" + L.<> nest 2 (hardline L.<> exampleCfg) + ) + + pathParser :: Parser FilePath + pathParser = + strArgument $ + metavar "FILENAME" <> + help "Path to configuration file" + + exampleCfg :: Doc + exampleCfg = vsep . map (text . toS) . lines $ + [str|db-uri = "postgres://user:pass@localhost:5432/dbname" + |db-schema = "public" # this schema gets added to the search_path of every request + |db-anon-role = "postgres" + |db-pool = 10 + |db-pool-timeout = 10 + | + |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" + | + |## Notification channel for reloading the schema cache + |# db-channel = "pgrst" + |## Enable or disable the notification channel + |# db-channel-enabled = false + | + |## 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" + |# secret-is-base64 = false + |# jwt-aud = "your_audience_claim" + | + |## limit rows in response + |# max-rows = 1000 + | + |## stored proc to exec immediately after auth + |# pre-request = "stored_proc_name" + | + |## jspath to the role claim key + |# role-claim-key = ".role" + | + |## extra schemas to add to the search_path of every request + |# db-extra-search-path = "extensions, util" + | + |## stored proc that overrides the root "/" spec + |## it must be inside the db-schema + |# root-spec = "stored_proc_name" + | + |## content types to produce raw output + |# raw-media-types="image/png, image/jpg" + |] + + +-- | Parse the config file +readAppConfig :: FilePath -> IO AppConfig +readAppConfig cfgPath = do -- Now read the actual config file conf <- catches (C.load cfgPath) [ Handler (\(ex :: IOError) -> exitErr $ "Cannot open config file:\n\t" <> show ex) , Handler (\(C.ParseError err) -> exitErr $ "Error parsing config file:\n" <> err) ] - case C.runParser parseConfig conf of + case C.runParser (parseConfig cfgPath) conf of Left err -> exitErr $ "Error parsing config file:\n\t" <> err Right appConf -> return appConf where - parseConfig = + parseConfig path = AppConfig <$> reqString "db-uri" <*> reqString "db-anon-role" @@ -180,6 +258,7 @@ readOptions = do <*> (maybe ["public"] splitOnCommas <$> optValue "db-extra-search-path") <*> optString "root-spec" <*> (maybe [] (fmap encodeUtf8 . splitOnCommas) <$> optValue "raw-media-types") + <*> pure (Just path) parseSocketFileMode :: C.Key -> C.Parser C.Config (Either Text FileMode) parseSocketFileMode k = @@ -243,79 +322,7 @@ readOptions = do splitOnCommas (C.String s) = strip <$> splitOn "," s splitOnCommas _ = [] - opts = info (helper <*> pathParser) $ - fullDesc - <> progDesc ( - "PostgREST " - <> toS prettyVersion - <> " / create a REST API to an existing Postgres database" - ) - <> footerDoc (Just $ - text "Example Config File:" - L.<> nest 2 (hardline L.<> exampleCfg) - ) - - parserPrefs = prefs showHelpOnError - exitErr :: Text -> IO a exitErr err = do hPutStrLn stderr err exitFailure - - exampleCfg :: Doc - exampleCfg = vsep . map (text . toS) . lines $ - [str|db-uri = "postgres://user:pass@localhost:5432/dbname" - |db-schema = "public" # this schema gets added to the search_path of every request - |db-anon-role = "postgres" - |db-pool = 10 - |db-pool-timeout = 10 - | - |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" - | - |## Notification channel for reloading the schema cache - |# db-channel = "pgrst" - |## Enable or disable the notification channel - |# db-channel-enabled = false - | - |## 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" - |# secret-is-base64 = false - |# jwt-aud = "your_audience_claim" - | - |## limit rows in response - |# max-rows = 1000 - | - |## stored proc to exec immediately after auth - |# pre-request = "stored_proc_name" - | - |## jspath to the role claim key - |# role-claim-key = ".role" - | - |## extra schemas to add to the search_path of every request - |# db-extra-search-path = "extensions, util" - | - |## stored proc that overrides the root "/" spec - |## it must be inside the db-schema - |# root-spec = "stored_proc_name" - | - |## content types to produce raw output - |# raw-media-types="image/png, image/jpg" - |] - -pathParser :: Parser FilePath -pathParser = - strArgument $ - metavar "FILENAME" <> - help "Path to configuration file" diff --git a/test/SpecHelper.hs b/test/SpecHelper.hs index 260e07603..98762babe 100644 --- a/test/SpecHelper.hs +++ b/test/SpecHelper.hs @@ -90,6 +90,8 @@ _baseCfg = -- Connection Settings Nothing -- Raw output media types [] + -- Config path + Nothing testCfg :: Text -> AppConfig testCfg testDbConn = _baseCfg { configDbUri = testDbConn }