server: add connection_template as an argument in pg_test_connection_template API

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8520
GitOrigin-RevId: 525bba9015ad4e143e94124e42ec4518252932cb
This commit is contained in:
pranshi06 2023-04-03 19:55:39 +05:30 committed by hasura-bot
parent 7cc33dd8ec
commit e6c8abf7d4
5 changed files with 72 additions and 23 deletions

View File

@ -301,17 +301,21 @@ X-Hasura-Role: admin
"operation_type": "query", "operation_type": "query",
"operation_name": "users_by_pk" "operation_name": "users_by_pk"
} }
} },
"connection_template": {
"template": "{{ if $.request.session?[\"x-hasura-role\"] == \"user\" }} {{$.connection_set.connection_set_member_name}} {{else}} {{$.primary}} {{ end }}"
}
} }
} }
``` ```
### Args syntax {#metadata-pg-test-connection-template-syntax} ### Args syntax {#metadata-pg-test-connection-template-syntax}
| Key | Required | Schema | Description | | Key | Required | Schema | Description |
| --------------- | -------- | --------------------------------------------------------------- | ------------------------------------------------------------- | | ------------------- | --------- | --------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| source_name | false | [SourceName](/api-reference/syntax-defs.mdx#sourcename) | Name of the source database of the table (default: `default`) | | source_name | false | [SourceName](/api-reference/syntax-defs.mdx#sourcename) | Name of the source database of the table (default: `default`) |
| request_context | true | [RequestContext](/api-reference/syntax-defs.mdx#requestcontext) | Request context | | request_context | true | [RequestContext](/api-reference/syntax-defs.mdx#requestcontext) | Request context |
| connection_template | false | [PGConnectionTemplate](/api-reference/syntax-defs.mdx#pgconnectiontemplate) | DB connection template |
:::info Enterprise only :::info Enterprise only

View File

@ -354,7 +354,7 @@ X-Hasura-Role: admin
{ {
"type": "pg_test_connection_template", "type": "pg_test_connection_template",
"args": { "args": {
"source": "source_name", "source_name": "source_name",
"request_context": { "request_context": {
"headers": { "headers": {
"header_name": "header_value" "header_name": "header_value"
@ -366,7 +366,10 @@ X-Hasura-Role: admin
"operation_type": "query", "operation_type": "query",
"operation_name": "op_name" "operation_name": "op_name"
} }
} },
"connection_template": {
"template": "{{ if $.request.session?[\"x-hasura-role\"] == \"user\" }} {{$.primary}} {{else}} {{$.connection_set.db_1}} {{ end }}"
}
} }
} }
``` ```
@ -382,6 +385,14 @@ Success Response:
} }
``` ```
:::info Note
`connection_template` is an optional argument which has precedence over the connection template present in the source. If
`connection_template` is provided in the API, then the source's connection template will be ignored.
:::
## Limitations ## Limitations
### Postgres schema of connection set ### Postgres schema of connection set

View File

@ -23,6 +23,7 @@ module Hasura.Backends.Postgres.Execute.Types
runPgSourceWriteTx, runPgSourceWriteTx,
applyConnectionTemplateResolverNonAdmin, applyConnectionTemplateResolverNonAdmin,
pgResolveConnectionTemplate, pgResolveConnectionTemplate,
resolvePostgresConnectionTemplate,
) )
where where
@ -33,7 +34,7 @@ import Data.CaseInsensitive qualified as CI
import Data.HashMap.Internal.Strict qualified as Map import Data.HashMap.Internal.Strict qualified as Map
import Database.PG.Query qualified as PG import Database.PG.Query qualified as PG
import Database.PG.Query.Connection qualified as PG import Database.PG.Query.Connection qualified as PG
import Hasura.Backends.Postgres.Connection.Settings (PostgresConnectionSetMemberName) import Hasura.Backends.Postgres.Connection.Settings (ConnectionTemplate (..), PostgresConnectionSetMemberName)
import Hasura.Backends.Postgres.Execute.ConnectionTemplate import Hasura.Backends.Postgres.Execute.ConnectionTemplate
import Hasura.Backends.Postgres.SQL.Error import Hasura.Backends.Postgres.SQL.Error
import Hasura.Base.Error import Hasura.Base.Error
@ -42,6 +43,7 @@ import Hasura.Prelude
import Hasura.RQL.Types.ResizePool import Hasura.RQL.Types.ResizePool
import Hasura.SQL.Types (ExtensionsSchema) import Hasura.SQL.Types (ExtensionsSchema)
import Hasura.Session import Hasura.Session
import Kriti.Error qualified as Kriti
import Network.HTTP.Types qualified as HTTP import Network.HTTP.Types qualified as HTTP
-- See Note [Existentially Quantified Types] -- See Note [Existentially Quantified Types]
@ -270,20 +272,48 @@ applyConnectionTemplateResolver connectionTemplateResolver sessionVariables requ
for connectionTemplateResolver $ \resolver -> for connectionTemplateResolver $ \resolver ->
_runResolver resolver sessionVariables requestHeaders queryContext _runResolver resolver sessionVariables requestHeaders queryContext
pgResolveConnectionTemplate :: (MonadError QErr m) => PGSourceConfig -> RequestContext -> m EncJSON pgResolveConnectionTemplate :: (MonadError QErr m) => PGSourceConfig -> RequestContext -> Maybe ConnectionTemplate -> m EncJSON
pgResolveConnectionTemplate sourceConfig (RequestContext (RequestContextHeaders headersMap) sessionVariables queryContext) = do pgResolveConnectionTemplate sourceConfig (RequestContext (RequestContextHeaders headersMap) sessionVariables queryContext) connectionTemplateMaybe = do
let headers = map (\(hName, hVal) -> (CI.mk (txtToBs hName), txtToBs hVal)) $ Map.toList headersMap
connectionTemplateResolver <- connectionTemplateResolver <-
case _pscConnectionTemplateConfig sourceConfig of case connectionTemplateMaybe of
ConnTemplate_NotApplicable -> Nothing ->
throw400 NotSupported "Connection templating feature is enterprise edition only" case _pscConnectionTemplateConfig sourceConfig of
ConnTemplate_NotConfigured -> ConnTemplate_NotApplicable -> connectionTemplateNotApplicableError
throw400 TemplateResolutionFailed "Connection template not defined for the source" ConnTemplate_NotConfigured ->
ConnTemplate_Resolver resolver -> throw400 TemplateResolutionFailed "Connection template not defined for the source"
pure resolver ConnTemplate_Resolver resolver ->
pure resolver
Just connectionTemplate ->
case _pscConnectionTemplateConfig sourceConfig of
-- connection template is an enterprise edition only feature. `ConnTemplate_NotApplicable` error is thrown
-- when community edition engine is used to test the connection template
ConnTemplate_NotApplicable -> connectionTemplateNotApplicableError
_ -> pure $ ConnectionTemplateResolver $ \sessionVariables' reqHeaders queryContext' ->
resolvePostgresConnectionTemplate connectionTemplate (Map.keys (_pscConnectionSet sourceConfig)) sessionVariables' reqHeaders queryContext'
let headers = map (\(hName, hVal) -> (CI.mk (txtToBs hName), txtToBs hVal)) $ Map.toList headersMap
case maybeRoleFromSessionVariables sessionVariables of case maybeRoleFromSessionVariables sessionVariables of
Nothing -> throw400 InvalidParams "No `x-hasura-role` found in session variables. Please try again with non-admin 'x-hasura-role' in the session context." Nothing -> throw400 InvalidParams "No `x-hasura-role` found in session variables. Please try again with non-admin 'x-hasura-role' in the session context."
Just roleName -> Just roleName ->
when (roleName == adminRoleName) $ throw400 InvalidParams "Only requests made with a non-admin context can resolve the connection template. Please try again with non-admin 'x-hasura-role' in the session context." when (roleName == adminRoleName) $ throw400 InvalidParams "Only requests made with a non-admin context can resolve the connection template. Please try again with non-admin 'x-hasura-role' in the session context."
resolvedTemplate <- _runResolver connectionTemplateResolver sessionVariables headers queryContext resolvedTemplate <- _runResolver connectionTemplateResolver sessionVariables headers queryContext
pure . encJFromJValue $ J.object ["result" J..= resolvedTemplate] pure . encJFromJValue $ J.object ["result" J..= resolvedTemplate]
where
connectionTemplateNotApplicableError = throw400 NotSupported "Connection templating feature is enterprise edition only"
resolvePostgresConnectionTemplate ::
(MonadError QErr m) =>
ConnectionTemplate ->
[PostgresConnectionSetMemberName] ->
SessionVariables ->
[HTTP.Header] ->
Maybe QueryContext ->
m (PostgresResolvedConnectionTemplate)
resolvePostgresConnectionTemplate (ConnectionTemplate _templateSrc connectionTemplate) connectionSetMembers sessionVariables reqHeaders queryContext = do
let requestContext = makeRequestContext queryContext reqHeaders sessionVariables
connectionTemplateCtx = makeConnectionTemplateContext requestContext connectionSetMembers
case runKritiEval connectionTemplateCtx connectionTemplate of
Left err ->
let serializedErr = Kriti.serialize err
in throw400WithDetail TemplateResolutionFailed ("Connection template evaluation failed: " <> Kriti._message serializedErr) (J.toJSON $ serializedErr)
Right val -> runAesonParser (J.parseJSON @PostgresResolvedConnectionTemplate) val

View File

@ -12,6 +12,7 @@ where
import Data.Aeson (FromJSON (parseJSON), ToJSON (toJSON)) import Data.Aeson (FromJSON (parseJSON), ToJSON (toJSON))
import Data.Aeson qualified as J import Data.Aeson qualified as J
import Hasura.Backends.Postgres.Connection.Settings (ConnectionTemplate (..))
import Hasura.Base.Error import Hasura.Base.Error
import Hasura.EncJSON import Hasura.EncJSON
import Hasura.Prelude import Hasura.Prelude
@ -24,7 +25,8 @@ import Hasura.SQL.AnyBackend qualified as AB
-- | The input type for the metadata API `<backend>_test_connection_template` -- | The input type for the metadata API `<backend>_test_connection_template`
data TestConnectionTemplate b = TestConnectionTemplate data TestConnectionTemplate b = TestConnectionTemplate
{ _tctSourceName :: SourceName, { _tctSourceName :: SourceName,
_tctRequestContext :: ConnectionTemplateRequestContext b _tctRequestContext :: ConnectionTemplateRequestContext b,
_tctConnectionTemplate :: (Maybe ConnectionTemplate)
} }
instance (Backend b) => FromJSON (TestConnectionTemplate b) where instance (Backend b) => FromJSON (TestConnectionTemplate b) where
@ -33,6 +35,7 @@ instance (Backend b) => FromJSON (TestConnectionTemplate b) where
TestConnectionTemplate TestConnectionTemplate
<$> o J..:? "source_name" J..!= defaultSource <$> o J..:? "source_name" J..!= defaultSource
<*> o J..: "request_context" <*> o J..: "request_context"
<*> o J..:? "connection_template"
-- | Resolver for the metadata API `<backend>_test_connection_template` -- | Resolver for the metadata API `<backend>_test_connection_template`
runTestConnectionTemplate :: runTestConnectionTemplate ::
@ -40,9 +43,9 @@ runTestConnectionTemplate ::
(MonadError QErr m, CacheRM m, Backend b, MetadataM m) => (MonadError QErr m, CacheRM m, Backend b, MetadataM m) =>
TestConnectionTemplate b -> TestConnectionTemplate b ->
m EncJSON m EncJSON
runTestConnectionTemplate (TestConnectionTemplate sourceName requestContext) = do runTestConnectionTemplate (TestConnectionTemplate sourceName requestContext connectionTemplateMaybe) = do
sourceConfig <- askSourceConfig @b sourceName sourceConfig <- askSourceConfig @b sourceName
liftEither $ resolveConnectionTemplate @b sourceConfig requestContext liftEither $ resolveConnectionTemplate @b sourceConfig requestContext connectionTemplateMaybe
-- A wrapper around the `ResolvedConnectionTemplate` for adding this to `query-log` -- A wrapper around the `ResolvedConnectionTemplate` for adding this to `query-log`
newtype ResolvedConnectionTemplateWrapper b = ResolvedConnectionTemplateWrapper newtype ResolvedConnectionTemplateWrapper b = ResolvedConnectionTemplateWrapper

View File

@ -21,6 +21,7 @@ import Data.Kind (Type)
import Data.Text.Casing (GQLNameIdentifier) import Data.Text.Casing (GQLNameIdentifier)
import Data.Text.Extended import Data.Text.Extended
import Data.Typeable (Typeable) import Data.Typeable (Typeable)
import Hasura.Backends.Postgres.Connection.Settings (ConnectionTemplate (..))
import Hasura.Base.Error import Hasura.Base.Error
import Hasura.Base.ToErrorValue import Hasura.Base.ToErrorValue
import Hasura.EncJSON (EncJSON) import Hasura.EncJSON (EncJSON)
@ -310,8 +311,8 @@ class
type ConnectionTemplateRequestContext b :: Type type ConnectionTemplateRequestContext b :: Type
type ConnectionTemplateRequestContext b = () -- Uninmplemented value type ConnectionTemplateRequestContext b = () -- Uninmplemented value
resolveConnectionTemplate :: SourceConfig b -> ConnectionTemplateRequestContext b -> Either QErr EncJSON resolveConnectionTemplate :: SourceConfig b -> ConnectionTemplateRequestContext b -> Maybe ConnectionTemplate -> Either QErr EncJSON
resolveConnectionTemplate _ _ = Left (err400 (NotSupported) "connection template is not implemented") resolveConnectionTemplate _ _ _ = Left (err400 (NotSupported) "connection template is not implemented")
-- | Information about the query execution that may be useful for debugging -- | Information about the query execution that may be useful for debugging
-- or reporting. -- or reporting.