mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
server: add the ability to force refresh of dynamic db connection str…
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/10713 GitOrigin-RevId: 7aa286141a27c11609614349cad05041e55b2a0f
This commit is contained in:
parent
5af867d4e0
commit
267d7fe751
@ -92,3 +92,16 @@ reference.
|
|||||||
|
|
||||||
Dynamic secrets can be used in template variables for data connectors. See
|
Dynamic secrets can be used in template variables for data connectors. See
|
||||||
[Template variables](/databases/database-config/data-connector-config.mdx/#template) for reference.
|
[Template variables](/databases/database-config/data-connector-config.mdx/#template) for reference.
|
||||||
|
|
||||||
|
## Forcing secret refresh
|
||||||
|
|
||||||
|
If the environment variable `HASURA_SECRETS_BLOCKING_FORCE_REFRESH_URL=<url>`
|
||||||
|
is set, on each connection failure the server will POST to the specified URL the payload:
|
||||||
|
|
||||||
|
```
|
||||||
|
{"filename": <path>}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is expected that the responding server will return only after refreshing the
|
||||||
|
secret at the given filepath. [hasura-secret-refresh](https://github.com/hasura/hasura-secret-refresh)
|
||||||
|
follows this spec.
|
||||||
|
@ -65,6 +65,9 @@ library
|
|||||||
, ekg-prometheus
|
, ekg-prometheus
|
||||||
, hashable
|
, hashable
|
||||||
, hashtables
|
, hashtables
|
||||||
|
-- for our HASURA_SECRETS_BLOCKING_FORCE_REFRESH_URL hook
|
||||||
|
, http-client
|
||||||
|
, http-types
|
||||||
, mmorph
|
, mmorph
|
||||||
, monad-control
|
, monad-control
|
||||||
, mtl
|
, mtl
|
||||||
|
@ -48,13 +48,14 @@ where
|
|||||||
|
|
||||||
import Control.Concurrent.Interrupt (interruptOnAsyncException)
|
import Control.Concurrent.Interrupt (interruptOnAsyncException)
|
||||||
import Control.Exception.Safe (Exception, SomeException (..), catch, throwIO)
|
import Control.Exception.Safe (Exception, SomeException (..), catch, throwIO)
|
||||||
|
import Control.Monad (unless)
|
||||||
import Control.Monad.Except (MonadError (throwError))
|
import Control.Monad.Except (MonadError (throwError))
|
||||||
import Control.Monad.IO.Class (MonadIO (liftIO))
|
import Control.Monad.IO.Class (MonadIO (liftIO))
|
||||||
import Control.Monad.Trans.Class (lift)
|
import Control.Monad.Trans.Class (lift)
|
||||||
import Control.Monad.Trans.Except (ExceptT, runExceptT, withExceptT)
|
import Control.Monad.Trans.Except (ExceptT, runExceptT, withExceptT)
|
||||||
import Control.Retry (RetryPolicyM)
|
import Control.Retry (RetryPolicyM)
|
||||||
import Control.Retry qualified as Retry
|
import Control.Retry qualified as Retry
|
||||||
import Data.Aeson (ToJSON (toJSON), Value (String), genericToJSON, object, (.=))
|
import Data.Aeson (ToJSON (toJSON), Value (String), encode, genericToJSON, object, (.=))
|
||||||
import Data.Aeson.Casing (aesonDrop, snakeCase)
|
import Data.Aeson.Casing (aesonDrop, snakeCase)
|
||||||
import Data.Aeson.TH (mkToJSON)
|
import Data.Aeson.TH (mkToJSON)
|
||||||
import Data.Bool (bool)
|
import Data.Bool (bool)
|
||||||
@ -77,6 +78,9 @@ import Data.Word (Word16, Word32)
|
|||||||
import Database.PostgreSQL.LibPQ qualified as PQ
|
import Database.PostgreSQL.LibPQ qualified as PQ
|
||||||
import Database.PostgreSQL.Simple.Options qualified as Options
|
import Database.PostgreSQL.Simple.Options qualified as Options
|
||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
|
import Network.HTTP.Client
|
||||||
|
import Network.HTTP.Types.Status (statusCode)
|
||||||
|
import System.Environment (lookupEnv)
|
||||||
import Prelude
|
import Prelude
|
||||||
|
|
||||||
{-# ANN module ("HLint: ignore Use tshow" :: String) #-}
|
{-# ANN module ("HLint: ignore Use tshow" :: String) #-}
|
||||||
@ -209,6 +213,7 @@ readConnErr conn = do
|
|||||||
pgRetrying ::
|
pgRetrying ::
|
||||||
(MonadIO m) =>
|
(MonadIO m) =>
|
||||||
Maybe String ->
|
Maybe String ->
|
||||||
|
-- | An action to perform on error
|
||||||
IO () ->
|
IO () ->
|
||||||
PGRetryPolicyM m ->
|
PGRetryPolicyM m ->
|
||||||
PGLogger ->
|
PGLogger ->
|
||||||
@ -242,6 +247,36 @@ initPQConn ::
|
|||||||
IO PQ.Connection
|
IO PQ.Connection
|
||||||
initPQConn ci logger = do
|
initPQConn ci logger = do
|
||||||
host <- extractHost (ciDetails ci)
|
host <- extractHost (ciDetails ci)
|
||||||
|
-- if this is a dynamic connection, we'll signal to refresh the secret (if
|
||||||
|
-- configured) during each retry, ensuring we don't make too many connection
|
||||||
|
-- attempts with the wrong credentials and risk getting locked out
|
||||||
|
resetFn <- do
|
||||||
|
mbUrl <- lookupEnv "HASURA_SECRETS_BLOCKING_FORCE_REFRESH_URL"
|
||||||
|
case (mbUrl, ciDetails ci) of
|
||||||
|
(Just url, CDDynamicDatabaseURI path) -> do
|
||||||
|
manager <- newManager defaultManagerSettings
|
||||||
|
|
||||||
|
-- Create the request
|
||||||
|
let body = encode $ object ["filename" .= path]
|
||||||
|
initialRequest <- parseRequest url
|
||||||
|
let request =
|
||||||
|
initialRequest
|
||||||
|
{ method = "POST",
|
||||||
|
requestBody = RequestBodyLBS body,
|
||||||
|
requestHeaders = [("Content-Type", "application/json")]
|
||||||
|
}
|
||||||
|
|
||||||
|
-- The action to perform on each retry. This must only return after
|
||||||
|
-- the secrets file has been refreshed.
|
||||||
|
return $ do
|
||||||
|
status <- statusCode . responseStatus <$> httpLbs request manager
|
||||||
|
unless (status >= 200 && status < 300) $
|
||||||
|
logger $
|
||||||
|
PLERetryMsg $
|
||||||
|
object
|
||||||
|
["message" .= String "Forcing refresh of secret file at HASURA_SECRETS_BLOCKING_FORCE_REFRESH_URL seems to have failed. Retrying anyway."]
|
||||||
|
_ -> pure $ pure ()
|
||||||
|
|
||||||
-- Retry if postgres connection error occurs
|
-- Retry if postgres connection error occurs
|
||||||
pgRetrying host resetFn retryP logger $ do
|
pgRetrying host resetFn retryP logger $ do
|
||||||
-- Initialise the connection
|
-- Initialise the connection
|
||||||
@ -252,7 +287,6 @@ initPQConn ci logger = do
|
|||||||
let connOk = s == PQ.ConnectionOk
|
let connOk = s == PQ.ConnectionOk
|
||||||
bool (whenConnNotOk conn) (whenConnOk conn) connOk
|
bool (whenConnNotOk conn) (whenConnOk conn) connOk
|
||||||
where
|
where
|
||||||
resetFn = return ()
|
|
||||||
retryP = mkPGRetryPolicy $ ciRetries ci
|
retryP = mkPGRetryPolicy $ ciRetries ci
|
||||||
|
|
||||||
whenConnNotOk conn = Left . PGConnErr <$> readConnErr conn
|
whenConnNotOk conn = Left . PGConnErr <$> readConnErr conn
|
||||||
|
Loading…
Reference in New Issue
Block a user