2019-11-26 15:14:21 +03:00
|
|
|
{-# LANGUAGE UndecidableInstances #-}
|
|
|
|
|
|
|
|
module Hasura.App where
|
|
|
|
|
2020-07-14 22:00:58 +03:00
|
|
|
import Control.Concurrent.STM.TVar (readTVarIO, TVar)
|
|
|
|
import Control.Exception (throwIO)
|
2020-06-19 09:42:32 +03:00
|
|
|
import Control.Lens (view, _2)
|
2019-11-26 15:14:21 +03:00
|
|
|
import Control.Monad.Base
|
2020-07-14 22:00:58 +03:00
|
|
|
import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow, onException, Exception)
|
2020-07-15 13:40:48 +03:00
|
|
|
import Control.Monad.Morph (hoist)
|
2019-11-26 15:14:21 +03:00
|
|
|
import Control.Monad.Stateless
|
2020-06-19 09:42:32 +03:00
|
|
|
import Control.Monad.STM (atomically)
|
|
|
|
import Control.Monad.Trans.Control (MonadBaseControl (..))
|
|
|
|
import Data.Aeson ((.=))
|
|
|
|
import Data.Time.Clock (UTCTime)
|
2020-03-18 04:31:22 +03:00
|
|
|
import GHC.AssertNF
|
2020-07-09 04:25:36 +03:00
|
|
|
import GHC.Stats
|
2019-11-26 15:14:21 +03:00
|
|
|
import Options.Applicative
|
2020-07-14 22:00:58 +03:00
|
|
|
import System.Environment (getEnvironment)
|
2020-07-09 04:25:36 +03:00
|
|
|
import System.Mem (performMajorGC)
|
2020-06-19 09:42:32 +03:00
|
|
|
|
|
|
|
import qualified Control.Concurrent.Async.Lifted.Safe as LA
|
|
|
|
import qualified Control.Concurrent.Extended as C
|
|
|
|
import qualified Data.Aeson as A
|
|
|
|
import qualified Data.ByteString.Char8 as BC
|
|
|
|
import qualified Data.ByteString.Lazy.Char8 as BLC
|
|
|
|
import qualified Data.Set as Set
|
|
|
|
import qualified Data.Text as T
|
2020-07-14 22:00:58 +03:00
|
|
|
import qualified Data.Environment as Env
|
2020-06-19 09:42:32 +03:00
|
|
|
import qualified Data.Time.Clock as Clock
|
|
|
|
import qualified Data.Yaml as Y
|
|
|
|
import qualified Database.PG.Query as Q
|
|
|
|
import qualified Network.HTTP.Client as HTTP
|
|
|
|
import qualified Network.HTTP.Client.TLS as HTTP
|
|
|
|
import qualified Network.Wai.Handler.Warp as Warp
|
|
|
|
import qualified System.Log.FastLogger as FL
|
|
|
|
import qualified Text.Mustache.Compile as M
|
2020-07-14 22:00:58 +03:00
|
|
|
import qualified Control.Immortal as Immortal
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2020-07-15 13:40:48 +03:00
|
|
|
import Hasura.GraphQL.Transport.HTTP (MonadExecuteQuery(..))
|
2019-11-26 15:14:21 +03:00
|
|
|
import Hasura.Db
|
|
|
|
import Hasura.EncJSON
|
2020-05-13 15:33:16 +03:00
|
|
|
import Hasura.Eventing.EventTrigger
|
2020-07-02 14:57:09 +03:00
|
|
|
import Hasura.Eventing.Common
|
2020-05-13 15:33:16 +03:00
|
|
|
import Hasura.Eventing.ScheduledTrigger
|
2020-06-19 09:42:32 +03:00
|
|
|
import Hasura.GraphQL.Execute (MonadGQLExecutionCheck (..),
|
|
|
|
checkQueryInAllowlist)
|
|
|
|
import Hasura.GraphQL.Logging (MonadQueryLog (..), QueryLog (..))
|
|
|
|
import Hasura.GraphQL.Resolve.Action (asyncActionsProcessor)
|
|
|
|
import Hasura.GraphQL.Transport.HTTP.Protocol (toParsed)
|
2019-11-26 15:14:21 +03:00
|
|
|
import Hasura.Logging
|
|
|
|
import Hasura.Prelude
|
2020-06-19 09:42:32 +03:00
|
|
|
import Hasura.RQL.DDL.Schema.Cache
|
|
|
|
import Hasura.RQL.Types (CacheRWM, Code (..), HasHttpManager,
|
|
|
|
HasSQLGenCtx, HasSystemDefined,
|
|
|
|
QErr (..), SQLGenCtx (..),
|
|
|
|
SchemaCache (..), UserInfoM,
|
|
|
|
buildSchemaCacheStrict, decodeValue,
|
|
|
|
throw400, withPathK)
|
2019-12-09 01:17:39 +03:00
|
|
|
import Hasura.RQL.Types.Run
|
2020-06-19 09:42:32 +03:00
|
|
|
import Hasura.Server.API.Query (fetchLastUpdate, requiresAdmin,
|
|
|
|
runQueryM)
|
2019-11-26 15:14:21 +03:00
|
|
|
import Hasura.Server.App
|
|
|
|
import Hasura.Server.Auth
|
2020-06-19 09:42:32 +03:00
|
|
|
import Hasura.Server.CheckUpdates (checkForUpdates)
|
2019-11-26 15:14:21 +03:00
|
|
|
import Hasura.Server.Init
|
|
|
|
import Hasura.Server.Logging
|
2020-06-19 09:42:32 +03:00
|
|
|
import Hasura.Server.Migrate (migrateCatalog)
|
2019-11-26 15:14:21 +03:00
|
|
|
import Hasura.Server.SchemaUpdate
|
|
|
|
import Hasura.Server.Telemetry
|
|
|
|
import Hasura.Server.Version
|
2020-04-24 12:10:53 +03:00
|
|
|
import Hasura.Session
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2020-07-15 13:40:48 +03:00
|
|
|
import qualified Hasura.Tracing as Tracing
|
2020-06-19 09:42:32 +03:00
|
|
|
import qualified Hasura.GraphQL.Transport.WebSocket.Server as WS
|
|
|
|
|
2020-07-14 22:00:58 +03:00
|
|
|
data ExitCode
|
|
|
|
= InvalidEnvironmentVariableOptionsError
|
|
|
|
| InvalidDatabaseConnectionParamsError
|
|
|
|
| MetadataCatalogFetchingError
|
|
|
|
| AuthConfigurationError
|
|
|
|
| EventSubSystemError
|
|
|
|
| EventEnvironmentVariableError
|
|
|
|
| MetadataExportError
|
|
|
|
| MetadataCleanError
|
|
|
|
| DatabaseMigrationError
|
|
|
|
| ExecuteProcessError
|
|
|
|
| DowngradeProcessError
|
|
|
|
| UnexpectedHasuraError
|
|
|
|
| ExitFailureError Int
|
|
|
|
deriving Show
|
|
|
|
|
|
|
|
data ExitException
|
|
|
|
= ExitException
|
|
|
|
{ eeCode :: !ExitCode
|
|
|
|
, eeMessage :: !BC.ByteString
|
|
|
|
} deriving (Show)
|
|
|
|
|
|
|
|
instance Exception ExitException
|
|
|
|
|
|
|
|
printErrExit :: (MonadIO m) => forall a . ExitCode -> String -> m a
|
|
|
|
printErrExit reason = liftIO . throwIO . ExitException reason . BC.pack
|
|
|
|
|
|
|
|
printErrJExit :: (A.ToJSON a, MonadIO m) => forall b . ExitCode -> a -> m b
|
|
|
|
printErrJExit reason = liftIO . throwIO . ExitException reason . BLC.toStrict . A.encode
|
2019-11-26 15:14:21 +03:00
|
|
|
|
|
|
|
parseHGECommand :: EnabledLogTypes impl => Parser (RawHGECommand impl)
|
|
|
|
parseHGECommand =
|
|
|
|
subparser
|
|
|
|
( command "serve" (info (helper <*> (HCServe <$> serveOptionsParser))
|
|
|
|
( progDesc "Start the GraphQL Engine Server"
|
|
|
|
<> footerDoc (Just serveCmdFooter)
|
|
|
|
))
|
|
|
|
<> command "export" (info (pure HCExport)
|
|
|
|
( progDesc "Export graphql-engine's metadata to stdout" ))
|
|
|
|
<> command "clean" (info (pure HCClean)
|
|
|
|
( progDesc "Clean graphql-engine's metadata to start afresh" ))
|
|
|
|
<> command "execute" (info (pure HCExecute)
|
|
|
|
( progDesc "Execute a query" ))
|
2020-02-07 14:03:12 +03:00
|
|
|
<> command "downgrade" (info (HCDowngrade <$> downgradeOptionsParser)
|
|
|
|
(progDesc "Downgrade the GraphQL Engine schema to the specified version"))
|
2019-11-26 15:14:21 +03:00
|
|
|
<> command "version" (info (pure HCVersion)
|
|
|
|
(progDesc "Prints the version of GraphQL Engine"))
|
|
|
|
)
|
|
|
|
|
|
|
|
parseArgs :: EnabledLogTypes impl => IO (HGEOptions impl)
|
|
|
|
parseArgs = do
|
|
|
|
rawHGEOpts <- execParser opts
|
|
|
|
env <- getEnvironment
|
|
|
|
let eitherOpts = runWithEnv env $ mkHGEOptions rawHGEOpts
|
2020-07-14 22:00:58 +03:00
|
|
|
either (printErrExit InvalidEnvironmentVariableOptionsError) return eitherOpts
|
2019-11-26 15:14:21 +03:00
|
|
|
where
|
|
|
|
opts = info (helper <*> hgeOpts)
|
|
|
|
( fullDesc <>
|
|
|
|
header "Hasura GraphQL Engine: Realtime GraphQL API over Postgres with access control" <>
|
|
|
|
footerDoc (Just mainCmdFooter)
|
|
|
|
)
|
|
|
|
hgeOpts = HGEOptionsG <$> parseRawConnInfo <*> parseHGECommand
|
|
|
|
|
|
|
|
printJSON :: (A.ToJSON a, MonadIO m) => a -> m ()
|
|
|
|
printJSON = liftIO . BLC.putStrLn . A.encode
|
|
|
|
|
|
|
|
printYaml :: (A.ToJSON a, MonadIO m) => a -> m ()
|
|
|
|
printYaml = liftIO . BC.putStrLn . Y.encode
|
|
|
|
|
|
|
|
mkPGLogger :: Logger Hasura -> Q.PGLogger
|
|
|
|
mkPGLogger (Logger logger) (Q.PLERetryMsg msg) =
|
|
|
|
logger $ PGLog LevelWarn msg
|
|
|
|
|
|
|
|
|
|
|
|
-- | most of the required types for initializing graphql-engine
|
|
|
|
data InitCtx
|
|
|
|
= InitCtx
|
2020-06-03 00:27:14 +03:00
|
|
|
{ _icHttpManager :: !HTTP.Manager
|
|
|
|
, _icInstanceId :: !InstanceId
|
|
|
|
, _icLoggers :: !Loggers
|
|
|
|
, _icConnInfo :: !Q.ConnInfo
|
|
|
|
, _icPgPool :: !Q.PGPool
|
|
|
|
, _icShutdownLatch :: !ShutdownLatch
|
2020-06-19 09:42:32 +03:00
|
|
|
, _icSchemaCache :: !(RebuildableSchemaCache Run, Maybe UTCTime)
|
2019-11-26 15:14:21 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
-- | Collection of the LoggerCtx, the regular Logger and the PGLogger
|
|
|
|
-- TODO: better naming?
|
|
|
|
data Loggers
|
|
|
|
= Loggers
|
|
|
|
{ _lsLoggerCtx :: !(LoggerCtx Hasura)
|
|
|
|
, _lsLogger :: !(Logger Hasura)
|
|
|
|
, _lsPgLogger :: !Q.PGLogger
|
|
|
|
}
|
|
|
|
|
|
|
|
newtype AppM a = AppM { unAppM :: IO a }
|
2020-07-14 22:00:58 +03:00
|
|
|
deriving (Functor, Applicative, Monad, MonadIO, MonadBase IO, MonadBaseControl IO, MonadCatch, MonadThrow, MonadMask)
|
2019-11-26 15:14:21 +03:00
|
|
|
|
|
|
|
-- | this function initializes the catalog and returns an @InitCtx@, based on the command given
|
|
|
|
-- - for serve command it creates a proper PG connection pool
|
|
|
|
-- - for other commands, it creates a minimal pool
|
|
|
|
-- this exists as a separate function because the context (logger, http manager, pg pool) can be
|
|
|
|
-- used by other functions as well
|
|
|
|
initialiseCtx
|
2020-06-19 09:42:32 +03:00
|
|
|
:: (HasVersion, MonadIO m, MonadCatch m)
|
2020-07-14 22:00:58 +03:00
|
|
|
=> Env.Environment
|
|
|
|
-> HGECommand Hasura
|
2019-11-26 15:14:21 +03:00
|
|
|
-> RawConnInfo
|
|
|
|
-> m (InitCtx, UTCTime)
|
2020-07-14 22:00:58 +03:00
|
|
|
initialiseCtx env hgeCmd rci = do
|
2019-11-26 15:14:21 +03:00
|
|
|
initTime <- liftIO Clock.getCurrentTime
|
|
|
|
-- global http manager
|
|
|
|
httpManager <- liftIO $ HTTP.newManager HTTP.tlsManagerSettings
|
|
|
|
instanceId <- liftIO generateInstanceId
|
|
|
|
connInfo <- liftIO procConnInfo
|
2020-06-03 00:27:14 +03:00
|
|
|
latch <- liftIO newShutdownLatch
|
2020-06-19 09:42:32 +03:00
|
|
|
(loggers, pool, sqlGenCtx) <- case hgeCmd of
|
|
|
|
-- for the @serve@ command generate a regular PG pool
|
2019-11-26 15:14:21 +03:00
|
|
|
HCServe so@ServeOptions{..} -> do
|
|
|
|
l@(Loggers _ logger pgLogger) <- mkLoggers soEnabledLogTypes soLogLevel
|
|
|
|
-- log serve options
|
|
|
|
unLogger logger $ serveOptsToLog so
|
|
|
|
-- log postgres connection info
|
|
|
|
unLogger logger $ connInfoToLog connInfo
|
|
|
|
pool <- liftIO $ Q.initPGPool connInfo soConnParams pgLogger
|
2020-06-19 09:42:32 +03:00
|
|
|
pure (l, pool, SQLGenCtx soStringifyNum)
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2020-06-19 09:42:32 +03:00
|
|
|
-- for other commands generate a minimal PG pool
|
2019-11-26 15:14:21 +03:00
|
|
|
_ -> do
|
|
|
|
l@(Loggers _ _ pgLogger) <- mkLoggers defaultEnabledLogTypes LevelInfo
|
|
|
|
pool <- getMinimalPool pgLogger connInfo
|
2020-06-19 09:42:32 +03:00
|
|
|
pure (l, pool, SQLGenCtx False)
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2020-06-19 09:42:32 +03:00
|
|
|
res <- flip onException (flushLogger (_lsLoggerCtx loggers)) $
|
2020-07-14 22:00:58 +03:00
|
|
|
migrateCatalogSchema env (_lsLogger loggers) pool httpManager sqlGenCtx
|
2020-06-19 09:42:32 +03:00
|
|
|
pure (InitCtx httpManager instanceId loggers connInfo pool latch res, initTime)
|
2019-11-26 15:14:21 +03:00
|
|
|
where
|
|
|
|
procConnInfo =
|
2020-07-14 22:00:58 +03:00
|
|
|
either (printErrExit InvalidDatabaseConnectionParamsError . ("Fatal Error : " <>)) return $ mkConnInfo rci
|
2019-11-26 15:14:21 +03:00
|
|
|
|
|
|
|
getMinimalPool pgLogger ci = do
|
|
|
|
let connParams = Q.defaultConnParams { Q.cpConns = 1 }
|
|
|
|
liftIO $ Q.initPGPool ci connParams pgLogger
|
|
|
|
|
|
|
|
mkLoggers enabledLogs logLevel = do
|
|
|
|
loggerCtx <- liftIO $ mkLoggerCtx (defaultLoggerSettings True logLevel) enabledLogs
|
|
|
|
let logger = mkLogger loggerCtx
|
|
|
|
pgLogger = mkPGLogger logger
|
|
|
|
return $ Loggers loggerCtx logger pgLogger
|
|
|
|
|
2020-06-19 09:42:32 +03:00
|
|
|
-- | helper function to initialize or migrate the @hdb_catalog@ schema (used by pro as well)
|
|
|
|
migrateCatalogSchema
|
|
|
|
:: (HasVersion, MonadIO m)
|
2020-07-14 22:00:58 +03:00
|
|
|
=> Env.Environment -> Logger Hasura -> Q.PGPool -> HTTP.Manager -> SQLGenCtx
|
2020-06-19 09:42:32 +03:00
|
|
|
-> m (RebuildableSchemaCache Run, Maybe UTCTime)
|
2020-07-14 22:00:58 +03:00
|
|
|
migrateCatalogSchema env logger pool httpManager sqlGenCtx = do
|
2020-06-19 09:42:32 +03:00
|
|
|
let pgExecCtx = mkPGExecCtx Q.Serializable pool
|
|
|
|
adminRunCtx = RunCtx adminUserInfo httpManager sqlGenCtx
|
|
|
|
currentTime <- liftIO Clock.getCurrentTime
|
|
|
|
initialiseResult <- runExceptT $ peelRun adminRunCtx pgExecCtx Q.ReadWrite $
|
2020-07-14 22:00:58 +03:00
|
|
|
(,) <$> migrateCatalog env currentTime <*> liftTx fetchLastUpdate
|
2020-06-19 09:42:32 +03:00
|
|
|
|
|
|
|
((migrationResult, schemaCache), lastUpdateEvent) <-
|
|
|
|
initialiseResult `onLeft` \err -> do
|
|
|
|
unLogger logger StartupLog
|
|
|
|
{ slLogLevel = LevelError
|
|
|
|
, slKind = "db_migrate"
|
|
|
|
, slInfo = A.toJSON err
|
|
|
|
}
|
2020-07-14 22:00:58 +03:00
|
|
|
liftIO (printErrExit DatabaseMigrationError (BLC.unpack $ A.encode err))
|
2020-06-19 09:42:32 +03:00
|
|
|
unLogger logger migrationResult
|
|
|
|
return (schemaCache, view _2 <$> lastUpdateEvent)
|
|
|
|
|
2020-04-01 18:14:26 +03:00
|
|
|
-- | Run a transaction and if an error is encountered, log the error and abort the program
|
|
|
|
runTxIO :: Q.PGPool -> Q.TxMode -> Q.TxE QErr a -> IO a
|
|
|
|
runTxIO pool isoLevel tx = do
|
|
|
|
eVal <- liftIO $ runExceptT $ Q.runTx pool isoLevel tx
|
2020-07-14 22:00:58 +03:00
|
|
|
either (printErrJExit DatabaseMigrationError) return eVal
|
2020-04-01 18:14:26 +03:00
|
|
|
|
2020-06-03 00:27:14 +03:00
|
|
|
-- | A latch for the graceful shutdown of a server process.
|
|
|
|
newtype ShutdownLatch = ShutdownLatch { unShutdownLatch :: C.MVar () }
|
|
|
|
|
|
|
|
newShutdownLatch :: IO ShutdownLatch
|
|
|
|
newShutdownLatch = fmap ShutdownLatch C.newEmptyMVar
|
|
|
|
|
|
|
|
-- | Block the current thread, waiting on the latch.
|
|
|
|
waitForShutdown :: ShutdownLatch -> IO ()
|
|
|
|
waitForShutdown = C.takeMVar . unShutdownLatch
|
|
|
|
|
2020-06-16 18:23:06 +03:00
|
|
|
-- | Initiate a graceful shutdown of the server associated with the provided
|
2020-06-03 00:27:14 +03:00
|
|
|
-- latch.
|
|
|
|
shutdownGracefully :: InitCtx -> IO ()
|
2020-07-14 22:00:58 +03:00
|
|
|
shutdownGracefully = shutdownGracefully' . _icShutdownLatch
|
|
|
|
|
|
|
|
shutdownGracefully' :: ShutdownLatch -> IO ()
|
|
|
|
shutdownGracefully' = flip C.putMVar () . unShutdownLatch
|
2020-06-03 00:27:14 +03:00
|
|
|
|
2020-06-19 09:42:32 +03:00
|
|
|
-- | If an exception is encountered , flush the log buffer and
|
|
|
|
-- rethrow If we do not flush the log buffer on exception, then log lines
|
|
|
|
-- may be missed
|
|
|
|
-- See: https://github.com/hasura/graphql-engine/issues/4772
|
2020-07-14 22:00:58 +03:00
|
|
|
flushLogger :: MonadIO m => LoggerCtx impl -> m ()
|
|
|
|
flushLogger = liftIO . FL.flushLogStr . _lcLoggerSet
|
2020-06-19 09:42:32 +03:00
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
runHGEServer
|
2020-01-23 00:55:55 +03:00
|
|
|
:: ( HasVersion
|
|
|
|
, MonadIO m
|
2020-07-14 22:00:58 +03:00
|
|
|
, MonadMask m
|
2019-11-26 15:14:21 +03:00
|
|
|
, MonadStateless IO m
|
2020-06-16 18:23:06 +03:00
|
|
|
, LA.Forall (LA.Pure m)
|
2020-07-15 13:40:48 +03:00
|
|
|
, UserAuthentication (Tracing.TraceT m)
|
2019-11-26 15:14:21 +03:00
|
|
|
, HttpLog m
|
|
|
|
, ConsoleRenderer m
|
2020-07-14 22:00:58 +03:00
|
|
|
, MetadataApiAuthorization m
|
2020-06-16 18:23:06 +03:00
|
|
|
, MonadGQLExecutionCheck m
|
|
|
|
, MonadConfigApiHandler m
|
2020-07-14 22:00:58 +03:00
|
|
|
, MonadQueryLog m
|
2020-06-19 09:42:32 +03:00
|
|
|
, WS.MonadWSLog m
|
2020-07-15 13:40:48 +03:00
|
|
|
, MonadExecuteQuery m
|
|
|
|
, Tracing.HasReporter m
|
2019-11-26 15:14:21 +03:00
|
|
|
)
|
2020-07-14 22:00:58 +03:00
|
|
|
=> Env.Environment
|
|
|
|
-> ServeOptions impl
|
2019-11-26 15:14:21 +03:00
|
|
|
-> InitCtx
|
2020-06-16 20:44:59 +03:00
|
|
|
-> Maybe PGExecCtx
|
|
|
|
-- ^ An optional specialized pg exection context for executing queries
|
|
|
|
-- and mutations
|
2019-11-26 15:14:21 +03:00
|
|
|
-> UTCTime
|
|
|
|
-- ^ start time
|
2020-07-14 22:00:58 +03:00
|
|
|
-> IO ()
|
|
|
|
-- ^ shutdown function
|
2019-11-26 15:14:21 +03:00
|
|
|
-> m ()
|
2020-07-14 22:00:58 +03:00
|
|
|
runHGEServer env ServeOptions{..} InitCtx{..} pgExecCtx initTime shutdownApp = do
|
2020-06-16 20:44:59 +03:00
|
|
|
-- Comment this to enable expensive assertions from "GHC.AssertNF". These
|
|
|
|
-- will log lines to STDOUT containing "not in normal form". In the future we
|
|
|
|
-- could try to integrate this into our tests. For now this is a development
|
|
|
|
-- tool.
|
2020-03-18 04:31:22 +03:00
|
|
|
--
|
|
|
|
-- NOTE: be sure to compile WITHOUT code coverage, for this to work properly.
|
|
|
|
liftIO disableAssertNF
|
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
let sqlGenCtx = SQLGenCtx soStringifyNum
|
|
|
|
Loggers loggerCtx logger _ = _icLoggers
|
|
|
|
|
2020-05-19 17:48:49 +03:00
|
|
|
authModeRes <- runExceptT $ setupAuthMode soAdminSecret soAuthHook soJwtSecret soUnAuthRole
|
2019-11-28 12:03:14 +03:00
|
|
|
_icHttpManager logger
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2020-07-14 22:00:58 +03:00
|
|
|
authMode <- either (printErrExit AuthConfigurationError . T.unpack) return authModeRes
|
|
|
|
|
2020-07-09 04:25:36 +03:00
|
|
|
_idleGCThread <- C.forkImmortal "ourIdleGC" logger $ liftIO $
|
|
|
|
ourIdleGC logger (seconds 0.3) (seconds 10) (seconds 60)
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2020-07-14 22:00:58 +03:00
|
|
|
HasuraApp app cacheRef cacheInitTime stopWsServer <- flip onException (flushLogger loggerCtx) $
|
|
|
|
mkWaiApp env
|
|
|
|
soTxIso
|
2020-04-24 10:55:51 +03:00
|
|
|
logger
|
|
|
|
sqlGenCtx
|
|
|
|
soEnableAllowlist
|
|
|
|
_icPgPool
|
2020-06-16 20:44:59 +03:00
|
|
|
pgExecCtx
|
2020-04-24 10:55:51 +03:00
|
|
|
_icConnInfo
|
|
|
|
_icHttpManager
|
|
|
|
authMode
|
|
|
|
soCorsConfig
|
|
|
|
soEnableConsole
|
|
|
|
soConsoleAssetsDir
|
|
|
|
soEnableTelemetry
|
|
|
|
_icInstanceId
|
|
|
|
soEnabledAPIs
|
|
|
|
soLiveQueryOpts
|
|
|
|
soPlanCacheOptions
|
|
|
|
soResponseInternalErrorsConfig
|
2020-06-19 09:42:32 +03:00
|
|
|
_icSchemaCache
|
2019-11-26 15:14:21 +03:00
|
|
|
|
|
|
|
-- log inconsistent schema objects
|
|
|
|
inconsObjs <- scInconsistentObjs <$> liftIO (getSCFromRef cacheRef)
|
2019-11-20 21:21:30 +03:00
|
|
|
liftIO $ logInconsObjs logger inconsObjs
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2020-03-05 20:59:26 +03:00
|
|
|
-- start background threads for schema sync
|
2020-07-14 22:00:58 +03:00
|
|
|
(schemaSyncListenerThread, schemaSyncProcessorThread) <-
|
2020-03-05 20:59:26 +03:00
|
|
|
startSchemaSyncThreads sqlGenCtx _icPgPool logger _icHttpManager
|
|
|
|
cacheRef _icInstanceId cacheInitTime
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2020-07-14 22:00:58 +03:00
|
|
|
let
|
|
|
|
maxEvThrds = fromMaybe defaultMaxEventThreads soEventsHttpPoolSize
|
|
|
|
fetchI = milliseconds $ fromMaybe (Milliseconds defaultFetchInterval) soEventsFetchInterval
|
|
|
|
logEnvHeaders = soLogHeadersFromEnv
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2020-07-02 14:57:09 +03:00
|
|
|
lockedEventsCtx <- liftIO $ atomically $ initLockedEventsCtx
|
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
-- prepare event triggers data
|
|
|
|
prepareEvents _icPgPool logger
|
2020-01-16 04:56:57 +03:00
|
|
|
eventEngineCtx <- liftIO $ atomically $ initEventEngineCtx maxEvThrds fetchI
|
2019-11-26 15:14:21 +03:00
|
|
|
unLogger logger $ mkGenericStrLog LevelInfo "event_triggers" "starting workers"
|
2020-07-14 22:00:58 +03:00
|
|
|
|
|
|
|
_eventQueueThread <- C.forkImmortal "processEventQueue" logger $
|
2020-03-11 09:27:31 +03:00
|
|
|
processEventQueue logger logEnvHeaders
|
2020-07-02 14:57:09 +03:00
|
|
|
_icHttpManager _icPgPool (getSCFromRef cacheRef) eventEngineCtx lockedEventsCtx
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2020-02-13 20:38:23 +03:00
|
|
|
-- start a backgroud thread to handle async actions
|
2020-07-14 22:00:58 +03:00
|
|
|
asyncActionsThread <- C.forkImmortal "asyncActionsProcessor" logger $
|
|
|
|
asyncActionsProcessor env (_scrCache cacheRef) _icPgPool _icHttpManager
|
2020-02-13 20:38:23 +03:00
|
|
|
|
2020-05-13 15:33:16 +03:00
|
|
|
-- start a background thread to create new cron events
|
|
|
|
void $ liftIO $ C.forkImmortal "runCronEventsGenerator" logger $
|
|
|
|
runCronEventsGenerator logger _icPgPool (getSCFromRef cacheRef)
|
|
|
|
|
2020-07-02 14:57:09 +03:00
|
|
|
-- prepare scheduled triggers
|
|
|
|
prepareScheduledEvents _icPgPool logger
|
|
|
|
|
2020-05-13 15:33:16 +03:00
|
|
|
-- start a background thread to deliver the scheduled events
|
2020-07-14 22:00:58 +03:00
|
|
|
void $ C.forkImmortal "processScheduledTriggers" logger $
|
|
|
|
processScheduledTriggers env logger logEnvHeaders _icHttpManager _icPgPool (getSCFromRef cacheRef) lockedEventsCtx
|
2020-05-13 15:33:16 +03:00
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
-- start a background thread to check for updates
|
2020-07-14 22:00:58 +03:00
|
|
|
updateThread <- C.forkImmortal "checkForUpdates" logger $ liftIO $
|
2020-03-05 20:59:26 +03:00
|
|
|
checkForUpdates loggerCtx _icHttpManager
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2020-07-14 22:00:58 +03:00
|
|
|
-- startTelemetry logger serveOpts cacheRef initCtx
|
2019-11-26 15:14:21 +03:00
|
|
|
-- start a background thread for telemetry
|
|
|
|
when soEnableTelemetry $ do
|
|
|
|
unLogger logger $ mkGenericStrLog LevelInfo "telemetry" telemetryNotice
|
2020-07-14 22:00:58 +03:00
|
|
|
|
2020-04-09 10:41:24 +03:00
|
|
|
(dbId, pgVersion) <- liftIO $ runTxIO _icPgPool (Q.ReadCommitted, Nothing) $
|
|
|
|
(,) <$> getDbId <*> getPgVersion
|
2020-07-14 22:00:58 +03:00
|
|
|
|
2020-04-01 18:14:26 +03:00
|
|
|
void $ C.forkImmortal "runTelemetry" logger $ liftIO $
|
2020-04-09 10:41:24 +03:00
|
|
|
runTelemetry logger _icHttpManager (getSCFromRef cacheRef) dbId _icInstanceId pgVersion
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2020-07-14 22:00:58 +03:00
|
|
|
|
|
|
|
|
|
|
|
-- events has its own shutdown mechanism, used in 'shutdownHandler'
|
|
|
|
let immortalThreads = [schemaSyncListenerThread, schemaSyncProcessorThread, updateThread, asyncActionsThread]
|
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
finishTime <- liftIO Clock.getCurrentTime
|
|
|
|
let apiInitTime = realToFrac $ Clock.diffUTCTime finishTime initTime
|
|
|
|
unLogger logger $
|
|
|
|
mkGenericLog LevelInfo "server" $ StartupTimeInfo "starting API server" apiInitTime
|
2020-04-14 09:01:50 +03:00
|
|
|
let warpSettings = Warp.setPort soPort
|
|
|
|
. Warp.setHost soHost
|
|
|
|
. Warp.setGracefulShutdownTimeout (Just 30) -- 30s graceful shutdown
|
2020-07-14 22:00:58 +03:00
|
|
|
. Warp.setInstallShutdownHandler (shutdownHandler _icLoggers immortalThreads stopWsServer lockedEventsCtx _icPgPool)
|
2020-04-14 09:01:50 +03:00
|
|
|
$ Warp.defaultSettings
|
2019-11-26 15:14:21 +03:00
|
|
|
liftIO $ Warp.runSettings warpSettings app
|
|
|
|
|
|
|
|
where
|
2020-04-14 09:01:50 +03:00
|
|
|
-- | prepareEvents is a function to unlock all the events that are
|
|
|
|
-- locked and unprocessed, which is called while hasura is started.
|
|
|
|
-- Locked and unprocessed events can occur in 2 ways
|
|
|
|
-- 1.
|
|
|
|
-- Hasura's shutdown was not graceful in which all the fetched
|
|
|
|
-- events will remain locked and unprocessed(TODO: clean shutdown)
|
|
|
|
-- state.
|
|
|
|
-- 2.
|
|
|
|
-- There is another hasura instance which is processing events and
|
|
|
|
-- it will lock events to process them.
|
|
|
|
-- So, unlocking all the locked events might re-deliver an event(due to #2).
|
2019-11-26 15:14:21 +03:00
|
|
|
prepareEvents pool (Logger logger) = do
|
|
|
|
liftIO $ logger $ mkGenericStrLog LevelInfo "event_triggers" "preparing data"
|
2020-04-14 09:01:50 +03:00
|
|
|
res <- liftIO $ runTx pool (Q.ReadCommitted, Nothing) unlockAllEvents
|
2020-07-14 22:00:58 +03:00
|
|
|
either (printErrJExit EventSubSystemError) return res
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2020-07-02 14:57:09 +03:00
|
|
|
-- | prepareScheduledEvents is like prepareEvents, but for scheduled triggers
|
|
|
|
prepareScheduledEvents pool (Logger logger) = do
|
|
|
|
liftIO $ logger $ mkGenericStrLog LevelInfo "scheduled_triggers" "preparing data"
|
|
|
|
res <- liftIO $ runTx pool (Q.ReadCommitted, Nothing) unlockAllLockedScheduledEvents
|
2020-07-14 22:00:58 +03:00
|
|
|
either (printErrJExit EventSubSystemError) return res
|
2020-07-02 14:57:09 +03:00
|
|
|
|
2020-04-14 09:01:50 +03:00
|
|
|
-- | shutdownEvents will be triggered when a graceful shutdown has been inititiated, it will
|
2020-07-02 14:57:09 +03:00
|
|
|
-- get the locked events from the event engine context and the scheduled event engine context
|
|
|
|
-- then it will unlock all those events.
|
2020-04-14 09:01:50 +03:00
|
|
|
-- It may happen that an event may be processed more than one time, an event that has been already
|
|
|
|
-- processed but not been marked as delivered in the db will be unlocked by `shutdownEvents`
|
|
|
|
-- and will be processed when the events are proccessed next time.
|
2020-07-02 14:57:09 +03:00
|
|
|
shutdownEvents
|
|
|
|
:: Q.PGPool
|
|
|
|
-> Logger Hasura
|
|
|
|
-> LockedEventsCtx
|
|
|
|
-> IO ()
|
|
|
|
shutdownEvents pool hasuraLogger@(Logger logger) LockedEventsCtx {..} = do
|
2020-04-14 09:01:50 +03:00
|
|
|
liftIO $ logger $ mkGenericStrLog LevelInfo "event_triggers" "unlocking events that are locked by the HGE"
|
2020-07-02 14:57:09 +03:00
|
|
|
unlockEventsForShutdown pool hasuraLogger "event_triggers" "" unlockEvents leEvents
|
|
|
|
liftIO $ logger $ mkGenericStrLog LevelInfo "scheduled_triggers" "unlocking scheduled events that are locked by the HGE"
|
|
|
|
unlockEventsForShutdown pool hasuraLogger "scheduled_triggers" "cron events" unlockCronEvents leCronEvents
|
|
|
|
unlockEventsForShutdown pool hasuraLogger "scheduled_triggers" "scheduled events" unlockCronEvents leStandAloneEvents
|
|
|
|
|
|
|
|
unlockEventsForShutdown
|
|
|
|
:: Q.PGPool
|
|
|
|
-> Logger Hasura
|
|
|
|
-> Text -- ^ trigger type
|
|
|
|
-> Text -- ^ event type
|
|
|
|
-> ([eventId] -> Q.TxE QErr Int)
|
|
|
|
-> TVar (Set.Set eventId)
|
|
|
|
-> IO ()
|
|
|
|
unlockEventsForShutdown pool (Logger logger) triggerType eventType doUnlock lockedIdsVar = do
|
|
|
|
lockedIds <- readTVarIO lockedIdsVar
|
|
|
|
unless (Set.null lockedIds) do
|
|
|
|
result <- runTx pool (Q.ReadCommitted, Nothing) (doUnlock $ toList lockedIds)
|
|
|
|
case result of
|
|
|
|
Left err -> logger $ mkGenericStrLog LevelWarn triggerType $
|
|
|
|
"Error while unlocking " ++ T.unpack eventType ++ " events: " ++ show err
|
|
|
|
Right count -> logger $ mkGenericStrLog LevelInfo triggerType $
|
|
|
|
show count ++ " " ++ T.unpack eventType ++ " events successfully unlocked"
|
2020-04-14 09:01:50 +03:00
|
|
|
|
|
|
|
runTx :: Q.PGPool -> Q.TxMode -> Q.TxE QErr a -> IO (Either QErr a)
|
|
|
|
runTx pool txLevel tx =
|
|
|
|
liftIO $ runExceptT $ Q.runTx pool txLevel tx
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2020-06-03 00:27:14 +03:00
|
|
|
-- | Waits for the shutdown latch 'MVar' to be filled, and then
|
|
|
|
-- shuts down the server and associated resources.
|
|
|
|
-- Structuring things this way lets us decide elsewhere exactly how
|
2020-06-16 18:23:06 +03:00
|
|
|
-- we want to control shutdown.
|
2020-07-02 14:57:09 +03:00
|
|
|
shutdownHandler
|
|
|
|
:: Loggers
|
2020-07-14 22:00:58 +03:00
|
|
|
-> [Immortal.Thread]
|
2020-07-02 14:57:09 +03:00
|
|
|
-> IO ()
|
2020-07-14 22:00:58 +03:00
|
|
|
-- ^ the stop websocket server function
|
2020-07-02 14:57:09 +03:00
|
|
|
-> LockedEventsCtx
|
|
|
|
-> Q.PGPool
|
|
|
|
-> IO ()
|
2020-07-14 22:00:58 +03:00
|
|
|
-- ^ the closeSocket callback
|
2020-07-02 14:57:09 +03:00
|
|
|
-> IO ()
|
2020-07-14 22:00:58 +03:00
|
|
|
shutdownHandler (Loggers loggerCtx (Logger logger) _) immortalThreads stopWsServer leCtx pool closeSocket =
|
|
|
|
LA.link =<< LA.async do
|
2020-06-03 00:27:14 +03:00
|
|
|
waitForShutdown _icShutdownLatch
|
|
|
|
logger $ mkGenericStrLog LevelInfo "server" "gracefully shutting down server"
|
2020-07-02 14:57:09 +03:00
|
|
|
shutdownEvents pool (Logger logger) leCtx
|
2019-11-26 15:14:21 +03:00
|
|
|
closeSocket
|
2020-07-14 22:00:58 +03:00
|
|
|
stopWsServer
|
|
|
|
-- kill all the background immortal threads
|
|
|
|
logger $ mkGenericStrLog LevelInfo "server" "killing all background immortal threads"
|
|
|
|
forM_ immortalThreads $ \thread -> do
|
|
|
|
logger $ mkGenericStrLog LevelInfo "server" $ "killing thread: " <> show (Immortal.threadId thread)
|
|
|
|
Immortal.stop thread
|
2019-11-26 15:14:21 +03:00
|
|
|
shutdownApp
|
2020-05-19 10:49:30 +03:00
|
|
|
cleanLoggerCtx loggerCtx
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2020-07-09 04:25:36 +03:00
|
|
|
-- | The RTS's idle GC doesn't work for us:
|
|
|
|
--
|
|
|
|
-- - when `-I` is too low it may fire continuously causing scary high CPU
|
|
|
|
-- when idle among other issues (see #2565)
|
|
|
|
-- - when we set it higher it won't run at all leading to memory being
|
|
|
|
-- retained when idle (especially noticeable when users are benchmarking and
|
|
|
|
-- see memory stay high after finishing). In the theoretical worst case
|
|
|
|
-- there is such low haskell heap pressure that we never run finalizers to
|
|
|
|
-- free the foreign data from e.g. libpq.
|
|
|
|
-- - `-Iw` is not yet implemented in 8.10.1: https://gitlab.haskell.org/ghc/ghc/-/issues/18433
|
|
|
|
-- - even if it was these two knobs would still not give us a guarentee that
|
|
|
|
-- a major GC would always run at some minumum frequency (e.g. for finalizers)
|
|
|
|
--
|
|
|
|
-- ...so we hack together our own using GHC.Stats, which should have
|
|
|
|
-- insignificant runtime overhead.
|
|
|
|
ourIdleGC
|
|
|
|
:: Logger Hasura
|
|
|
|
-> DiffTime -- ^ Run a major GC when we've been "idle" for idleInterval
|
|
|
|
-> DiffTime -- ^ ...as long as it has been > minGCInterval time since the last major GC
|
|
|
|
-> DiffTime -- ^ Additionally, if it has been > maxNoGCInterval time, force a GC regardless.
|
|
|
|
-> IO void
|
|
|
|
ourIdleGC (Logger logger) idleInterval minGCInterval maxNoGCInterval =
|
|
|
|
startTimer >>= go 0 0
|
|
|
|
where
|
|
|
|
go gcs_prev major_gcs_prev timerSinceLastMajorGC = do
|
|
|
|
timeSinceLastGC <- timerSinceLastMajorGC
|
|
|
|
when (timeSinceLastGC < minGCInterval) $ do
|
|
|
|
-- no need to check idle until we've passed the minGCInterval:
|
|
|
|
C.sleep (minGCInterval - timeSinceLastGC)
|
|
|
|
|
|
|
|
RTSStats{gcs, major_gcs} <- getRTSStats
|
|
|
|
-- We use minor GCs as a proxy for "activity", which seems to work
|
|
|
|
-- well-enough (in tests it stays stable for a few seconds when we're
|
|
|
|
-- logically "idle" and otherwise increments quickly)
|
|
|
|
let areIdle = gcs == gcs_prev
|
|
|
|
areOverdue = timeSinceLastGC > maxNoGCInterval
|
|
|
|
|
|
|
|
-- a major GC was run since last iteration (cool!), reset timer:
|
|
|
|
if | major_gcs > major_gcs_prev -> do
|
|
|
|
startTimer >>= go gcs major_gcs
|
|
|
|
|
|
|
|
-- we are idle and its a good time to do a GC, or we're overdue and must run a GC:
|
|
|
|
| areIdle || areOverdue -> do
|
|
|
|
when (areOverdue && not areIdle) $
|
|
|
|
logger $ UnstructuredLog LevelWarn $
|
|
|
|
"Overdue for a major GC: forcing one even though we don't appear to be idle"
|
|
|
|
performMajorGC
|
|
|
|
startTimer >>= go (gcs+1) (major_gcs+1)
|
|
|
|
|
|
|
|
-- else keep the timer running, waiting for us to go idle:
|
|
|
|
| otherwise -> do
|
|
|
|
C.sleep idleInterval
|
|
|
|
go gcs major_gcs timerSinceLastMajorGC
|
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
runAsAdmin
|
|
|
|
:: (MonadIO m)
|
|
|
|
=> Q.PGPool
|
|
|
|
-> SQLGenCtx
|
|
|
|
-> HTTP.Manager
|
|
|
|
-> Run a
|
|
|
|
-> m (Either QErr a)
|
|
|
|
runAsAdmin pool sqlGenCtx httpManager m = do
|
2019-11-20 21:21:30 +03:00
|
|
|
let runCtx = RunCtx adminUserInfo httpManager sqlGenCtx
|
2020-06-16 20:44:59 +03:00
|
|
|
pgCtx = mkPGExecCtx Q.Serializable pool
|
2019-11-20 21:21:30 +03:00
|
|
|
runExceptT $ peelRun runCtx pgCtx Q.ReadWrite m
|
2019-11-26 15:14:21 +03:00
|
|
|
|
|
|
|
execQuery
|
2020-01-23 00:55:55 +03:00
|
|
|
:: ( HasVersion
|
|
|
|
, CacheRWM m
|
2019-11-26 15:14:21 +03:00
|
|
|
, MonadTx m
|
|
|
|
, MonadIO m
|
|
|
|
, HasHttpManager m
|
|
|
|
, HasSQLGenCtx m
|
|
|
|
, UserInfoM m
|
|
|
|
, HasSystemDefined m
|
2020-07-15 13:40:48 +03:00
|
|
|
, Tracing.MonadTrace m
|
2019-11-26 15:14:21 +03:00
|
|
|
)
|
2020-07-14 22:00:58 +03:00
|
|
|
=> Env.Environment
|
|
|
|
-> BLC.ByteString
|
2019-11-26 15:14:21 +03:00
|
|
|
-> m BLC.ByteString
|
2020-07-14 22:00:58 +03:00
|
|
|
execQuery env queryBs = do
|
2019-11-26 15:14:21 +03:00
|
|
|
query <- case A.decode queryBs of
|
|
|
|
Just jVal -> decodeValue jVal
|
|
|
|
Nothing -> throw400 InvalidJSON "invalid json"
|
|
|
|
buildSchemaCacheStrict
|
2020-07-14 22:00:58 +03:00
|
|
|
encJToLBS <$> runQueryM env query
|
2019-11-26 15:14:21 +03:00
|
|
|
|
2020-07-15 13:40:48 +03:00
|
|
|
instance Tracing.HasReporter AppM
|
|
|
|
|
2019-11-26 15:14:21 +03:00
|
|
|
instance HttpLog AppM where
|
|
|
|
logHttpError logger userInfoM reqId httpReq req qErr headers =
|
|
|
|
unLogger logger $ mkHttpLog $
|
|
|
|
mkHttpErrorLogContext userInfoM reqId httpReq qErr req Nothing Nothing headers
|
|
|
|
|
2019-12-17 00:02:05 +03:00
|
|
|
logHttpSuccess logger userInfoM reqId httpReq _ _ compressedResponse qTime cType headers =
|
2019-11-26 15:14:21 +03:00
|
|
|
unLogger logger $ mkHttpLog $
|
|
|
|
mkHttpAccessLogContext userInfoM reqId httpReq compressedResponse qTime cType headers
|
|
|
|
|
2020-07-15 13:40:48 +03:00
|
|
|
instance MonadExecuteQuery AppM where
|
|
|
|
executeQuery _ _ _ pgCtx _txAccess tx =
|
|
|
|
([],) <$> hoist (runQueryTx pgCtx) tx
|
|
|
|
|
|
|
|
instance UserAuthentication (Tracing.TraceT AppM) where
|
2019-11-26 15:14:21 +03:00
|
|
|
resolveUserInfo logger manager headers authMode =
|
2019-12-11 04:04:49 +03:00
|
|
|
runExceptT $ getUserInfoWithExpTime logger manager headers authMode
|
2019-11-26 15:14:21 +03:00
|
|
|
|
|
|
|
instance MetadataApiAuthorization AppM where
|
|
|
|
authorizeMetadataApi query userInfo = do
|
2020-04-24 12:10:53 +03:00
|
|
|
let currRole = _uiRole userInfo
|
|
|
|
when (requiresAdmin query && currRole /= adminRoleName) $
|
2019-11-26 15:14:21 +03:00
|
|
|
withPathK "args" $ throw400 AccessDenied errMsg
|
|
|
|
where
|
|
|
|
errMsg = "restricted access : admin only"
|
|
|
|
|
|
|
|
instance ConsoleRenderer AppM where
|
|
|
|
renderConsole path authMode enableTelemetry consoleAssetsDir =
|
|
|
|
return $ mkConsoleHTML path authMode enableTelemetry consoleAssetsDir
|
|
|
|
|
2020-06-16 18:23:06 +03:00
|
|
|
instance MonadGQLExecutionCheck AppM where
|
|
|
|
checkGQLExecution userInfo _ enableAL sc query = runExceptT $ do
|
|
|
|
req <- toParsed query
|
|
|
|
checkQueryInAllowlist enableAL userInfo req sc
|
|
|
|
return req
|
|
|
|
|
|
|
|
instance MonadConfigApiHandler AppM where
|
|
|
|
runConfigApiHandler = configApiGetHandler
|
|
|
|
|
2020-06-19 09:42:32 +03:00
|
|
|
instance MonadQueryLog AppM where
|
|
|
|
logQueryLog logger query genSqlM reqId =
|
|
|
|
unLogger logger $ QueryLog query genSqlM reqId
|
|
|
|
|
|
|
|
instance WS.MonadWSLog AppM where
|
|
|
|
logWSLog = unLogger
|
|
|
|
|
|
|
|
|
2020-06-16 18:23:06 +03:00
|
|
|
--- helper functions ---
|
|
|
|
|
2020-01-23 00:55:55 +03:00
|
|
|
mkConsoleHTML :: HasVersion => Text -> AuthMode -> Bool -> Maybe Text -> Either String Text
|
2019-11-26 15:14:21 +03:00
|
|
|
mkConsoleHTML path authMode enableTelemetry consoleAssetsDir =
|
|
|
|
renderHtmlTemplate consoleTmplt $
|
|
|
|
-- variables required to render the template
|
|
|
|
A.object [ "isAdminSecretSet" .= isAdminSecretSet authMode
|
|
|
|
, "consolePath" .= consolePath
|
|
|
|
, "enableTelemetry" .= boolToText enableTelemetry
|
|
|
|
, "cdnAssets" .= boolToText (isNothing consoleAssetsDir)
|
2020-01-23 00:55:55 +03:00
|
|
|
, "assetsVersion" .= consoleAssetsVersion
|
2019-11-26 15:14:21 +03:00
|
|
|
, "serverVersion" .= currentVersion
|
|
|
|
]
|
|
|
|
where
|
|
|
|
consolePath = case path of
|
|
|
|
"" -> "/console"
|
|
|
|
r -> "/console/" <> r
|
|
|
|
|
|
|
|
consoleTmplt = $(M.embedSingleTemplate "src-rsr/console.html")
|
|
|
|
|
|
|
|
telemetryNotice :: String
|
|
|
|
telemetryNotice =
|
|
|
|
"Help us improve Hasura! The graphql-engine server collects anonymized "
|
|
|
|
<> "usage stats which allows us to keep improving Hasura at warp speed. "
|
2020-02-27 13:13:07 +03:00
|
|
|
<> "To read more or opt-out, visit https://hasura.io/docs/1.0/graphql/manual/guides/telemetry.html"
|