mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-17 20:41:49 +03:00
cbe0479406
### Description There were several functions in `GraphQL.Schema.Common` that were unrelated to the schema building process, and were about metadata manipulation or dependency computation. Having those functions in the schema part of the code forces several places in the code to depend on the schema code, despite being completely unrelated. This PR moves those functions where they make sense: alongside similar functions in `RQL.Types.*`, and rewrites `getRemoteDependencies` for clarity (it was using the term "indirect dependency" in a way that was inconsistent with the rest of the code). PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4568 GitOrigin-RevId: 948a18cebbb337a8bb6367c1f2d2ef5628209d96
259 lines
9.1 KiB
Haskell
259 lines
9.1 KiB
Haskell
module Hasura.RQL.DDL.RemoteSchema
|
|
( runAddRemoteSchema,
|
|
runRemoveRemoteSchema,
|
|
dropRemoteSchemaInMetadata,
|
|
runReloadRemoteSchema,
|
|
addRemoteSchemaP1,
|
|
addRemoteSchemaP2Setup,
|
|
runIntrospectRemoteSchema,
|
|
dropRemoteSchemaPermissionInMetadata,
|
|
dropRemoteSchemaRemoteRelationshipInMetadata,
|
|
runAddRemoteSchemaPermissions,
|
|
runDropRemoteSchemaPermissions,
|
|
runUpdateRemoteSchema,
|
|
)
|
|
where
|
|
|
|
import Control.Lens ((^.))
|
|
import Data.Environment qualified as Env
|
|
import Data.HashMap.Strict qualified as Map
|
|
import Data.HashMap.Strict.InsOrd qualified as OMap
|
|
import Data.HashSet qualified as S
|
|
import Data.Text.Extended
|
|
import Hasura.Base.Error
|
|
import Hasura.EncJSON
|
|
import Hasura.GraphQL.RemoteServer
|
|
import Hasura.Prelude
|
|
import Hasura.RQL.DDL.RemoteSchema.Permission
|
|
import Hasura.RQL.Types.Common
|
|
import Hasura.RQL.Types.Metadata
|
|
import Hasura.RQL.Types.Metadata.Object
|
|
import Hasura.RQL.Types.RemoteSchema
|
|
import Hasura.RQL.Types.SchemaCache
|
|
import Hasura.RQL.Types.SchemaCache.Build
|
|
import Hasura.RQL.Types.SchemaCacheTypes
|
|
import Hasura.Server.Types
|
|
import Hasura.Session
|
|
import Hasura.Tracing qualified as Tracing
|
|
import Network.HTTP.Client.Manager (HasHttpManagerM (..))
|
|
|
|
runAddRemoteSchema ::
|
|
( QErrM m,
|
|
CacheRWM m,
|
|
MonadIO m,
|
|
HasHttpManagerM m,
|
|
MetadataM m,
|
|
Tracing.MonadTrace m
|
|
) =>
|
|
Env.Environment ->
|
|
AddRemoteSchemaQuery ->
|
|
m EncJSON
|
|
runAddRemoteSchema env q@(AddRemoteSchemaQuery name defn comment) = do
|
|
addRemoteSchemaP1 name
|
|
-- addRemoteSchemaP2 env q
|
|
void $ addRemoteSchemaP2Setup env q
|
|
buildSchemaCacheFor (MORemoteSchema name) $
|
|
MetadataModifier $ metaRemoteSchemas %~ OMap.insert name remoteSchemaMeta
|
|
pure successMsg
|
|
where
|
|
-- NOTE: permissions here are empty, manipulated via a separate API with
|
|
-- runAddRemoteSchemaPermissions below
|
|
remoteSchemaMeta = RemoteSchemaMetadata name defn comment mempty mempty
|
|
|
|
doesRemoteSchemaPermissionExist :: Metadata -> RemoteSchemaName -> RoleName -> Bool
|
|
doesRemoteSchemaPermissionExist metadata remoteSchemaName roleName =
|
|
any ((== roleName) . _rspmRole) $ metadata ^. (metaRemoteSchemas . ix remoteSchemaName . rsmPermissions)
|
|
|
|
runAddRemoteSchemaPermissions ::
|
|
( QErrM m,
|
|
CacheRWM m,
|
|
HasServerConfigCtx m,
|
|
MetadataM m
|
|
) =>
|
|
AddRemoteSchemaPermission ->
|
|
m EncJSON
|
|
runAddRemoteSchemaPermissions q = do
|
|
metadata <- getMetadata
|
|
remoteSchemaPermsCtx <- _sccRemoteSchemaPermsCtx <$> askServerConfigCtx
|
|
unless (remoteSchemaPermsCtx == RemoteSchemaPermsEnabled) $ do
|
|
throw400 ConstraintViolation $
|
|
"remote schema permissions can only be added when "
|
|
<> "remote schema permissions are enabled in the graphql-engine"
|
|
remoteSchemaMap <- scRemoteSchemas <$> askSchemaCache
|
|
remoteSchemaCtx <-
|
|
onNothing (Map.lookup name remoteSchemaMap) $
|
|
throw400 NotExists $ "remote schema " <> name <<> " doesn't exist"
|
|
when (doesRemoteSchemaPermissionExist metadata name role) $
|
|
throw400 AlreadyExists $
|
|
"permissions for role: " <> role <<> " for remote schema:"
|
|
<> name <<> " already exists"
|
|
void $ resolveRoleBasedRemoteSchema providedSchemaDoc remoteSchemaCtx
|
|
buildSchemaCacheFor (MORemoteSchemaPermissions name role) $
|
|
MetadataModifier $ metaRemoteSchemas . ix name . rsmPermissions %~ (:) remoteSchemaPermMeta
|
|
pure successMsg
|
|
where
|
|
AddRemoteSchemaPermission name role defn comment = q
|
|
|
|
remoteSchemaPermMeta = RemoteSchemaPermissionMetadata role defn comment
|
|
|
|
providedSchemaDoc = _rspdSchema defn
|
|
|
|
runDropRemoteSchemaPermissions ::
|
|
( QErrM m,
|
|
CacheRWM m,
|
|
MetadataM m
|
|
) =>
|
|
DropRemoteSchemaPermissions ->
|
|
m EncJSON
|
|
runDropRemoteSchemaPermissions (DropRemoteSchemaPermissions name roleName) = do
|
|
metadata <- getMetadata
|
|
remoteSchemaMap <- scRemoteSchemas <$> askSchemaCache
|
|
void $
|
|
onNothing (Map.lookup name remoteSchemaMap) $
|
|
throw400 NotExists $ "remote schema " <> name <<> " doesn't exist"
|
|
unless (doesRemoteSchemaPermissionExist metadata name roleName) $
|
|
throw400 NotExists $
|
|
"permissions for role: " <> roleName <<> " for remote schema:"
|
|
<> name <<> " doesn't exist"
|
|
buildSchemaCacheFor (MORemoteSchemaPermissions name roleName) $
|
|
dropRemoteSchemaPermissionInMetadata name roleName
|
|
pure successMsg
|
|
|
|
addRemoteSchemaP1 ::
|
|
(QErrM m, CacheRM m) =>
|
|
RemoteSchemaName ->
|
|
m ()
|
|
addRemoteSchemaP1 name = do
|
|
remoteSchemaNames <- getAllRemoteSchemas <$> askSchemaCache
|
|
when (name `elem` remoteSchemaNames) $
|
|
throw400 AlreadyExists $
|
|
"remote schema with name "
|
|
<> name <<> " already exists"
|
|
|
|
addRemoteSchemaP2Setup ::
|
|
(QErrM m, MonadIO m, HasHttpManagerM m, Tracing.MonadTrace m) =>
|
|
Env.Environment ->
|
|
AddRemoteSchemaQuery ->
|
|
m RemoteSchemaCtx
|
|
addRemoteSchemaP2Setup env (AddRemoteSchemaQuery name def _) = do
|
|
httpMgr <- askHttpManager
|
|
rsi <- validateRemoteSchemaDef env def
|
|
fetchRemoteSchema env httpMgr name rsi
|
|
|
|
runRemoveRemoteSchema ::
|
|
(QErrM m, UserInfoM m, CacheRWM m, MetadataM m) =>
|
|
RemoteSchemaNameQuery ->
|
|
m EncJSON
|
|
runRemoveRemoteSchema (RemoteSchemaNameQuery rsn) = do
|
|
void $ removeRemoteSchemaP1 rsn
|
|
withNewInconsistentObjsCheck $
|
|
buildSchemaCache $
|
|
dropRemoteSchemaInMetadata rsn
|
|
pure successMsg
|
|
|
|
removeRemoteSchemaP1 ::
|
|
(UserInfoM m, QErrM m, CacheRM m) =>
|
|
RemoteSchemaName ->
|
|
m [RoleName]
|
|
removeRemoteSchemaP1 rsn = do
|
|
sc <- askSchemaCache
|
|
let rmSchemas = scRemoteSchemas sc
|
|
void $
|
|
onNothing (Map.lookup rsn rmSchemas) $
|
|
throw400 NotExists "no such remote schema"
|
|
let depObjs = getDependentObjs sc remoteSchemaDepId
|
|
roles = mapMaybe getRole depObjs
|
|
nonPermDependentObjs = filter nonPermDependentObjPredicate depObjs
|
|
-- report non permission dependencies (if any), this happens
|
|
-- mostly when a remote relationship is defined with
|
|
-- the current remote schema
|
|
|
|
-- we only report the non permission dependencies because we
|
|
-- drop the related permissions
|
|
unless (null nonPermDependentObjs) $ reportDependentObjectsExist nonPermDependentObjs
|
|
pure roles
|
|
where
|
|
remoteSchemaDepId = SORemoteSchema rsn
|
|
|
|
getRole depObj =
|
|
case depObj of
|
|
SORemoteSchemaPermission _ role -> Just role
|
|
_ -> Nothing
|
|
|
|
nonPermDependentObjPredicate (SORemoteSchemaPermission _ _) = False
|
|
nonPermDependentObjPredicate _ = True
|
|
|
|
runReloadRemoteSchema ::
|
|
(QErrM m, CacheRWM m, MetadataM m) =>
|
|
RemoteSchemaNameQuery ->
|
|
m EncJSON
|
|
runReloadRemoteSchema (RemoteSchemaNameQuery name) = do
|
|
remoteSchemas <- getAllRemoteSchemas <$> askSchemaCache
|
|
unless (name `elem` remoteSchemas) $
|
|
throw400 NotExists $
|
|
"remote schema with name " <> name <<> " does not exist"
|
|
|
|
let invalidations = mempty {ciRemoteSchemas = S.singleton name}
|
|
metadata <- getMetadata
|
|
withNewInconsistentObjsCheck $
|
|
buildSchemaCacheWithOptions (CatalogUpdate Nothing) invalidations metadata
|
|
pure successMsg
|
|
|
|
runIntrospectRemoteSchema ::
|
|
(CacheRM m, QErrM m) => RemoteSchemaNameQuery -> m EncJSON
|
|
runIntrospectRemoteSchema (RemoteSchemaNameQuery rsName) = do
|
|
sc <- askSchemaCache
|
|
RemoteSchemaCtx {..} <-
|
|
Map.lookup rsName (scRemoteSchemas sc) `onNothing` throw400 NotExists ("remote schema: " <> rsName <<> " not found")
|
|
pure $ encJFromLBS _rscRawIntrospectionResult
|
|
|
|
runUpdateRemoteSchema ::
|
|
( QErrM m,
|
|
CacheRWM m,
|
|
MonadIO m,
|
|
HasHttpManagerM m,
|
|
MetadataM m,
|
|
Tracing.MonadTrace m
|
|
) =>
|
|
Env.Environment ->
|
|
AddRemoteSchemaQuery ->
|
|
m EncJSON
|
|
runUpdateRemoteSchema env (AddRemoteSchemaQuery name defn comment) = do
|
|
remoteSchemaNames <- getAllRemoteSchemas <$> askSchemaCache
|
|
remoteSchemaMap <- _metaRemoteSchemas <$> getMetadata
|
|
|
|
let metadataRMSchema = OMap.lookup name remoteSchemaMap
|
|
metadataRMSchemaPerms = maybe mempty _rsmPermissions metadataRMSchema
|
|
-- `metadataRMSchemaURL` and `metadataRMSchemaURLFromEnv` represent
|
|
-- details that were stored within the metadata
|
|
metadataRMSchemaURL = (_rsdUrl . _rsmDefinition) =<< metadataRMSchema
|
|
metadataRMSchemaURLFromEnv = (_rsdUrlFromEnv . _rsmDefinition) =<< metadataRMSchema
|
|
-- `currentRMSchemaURL` and `currentRMSchemaURLFromEnv` represent
|
|
-- the details that were provided in the request
|
|
currentRMSchemaURL = _rsdUrl defn
|
|
currentRMSchemaURLFromEnv = _rsdUrlFromEnv defn
|
|
|
|
unless (name `elem` remoteSchemaNames) $
|
|
throw400 NotExists $ "remote schema with name " <> name <<> " doesn't exist"
|
|
|
|
rsi <- validateRemoteSchemaDef env defn
|
|
|
|
-- we only proceed to fetch the remote schema if the url has been updated
|
|
unless
|
|
( (isJust metadataRMSchemaURL && isJust currentRMSchemaURL && metadataRMSchemaURL == currentRMSchemaURL)
|
|
|| (isJust metadataRMSchemaURLFromEnv && isJust currentRMSchemaURLFromEnv && metadataRMSchemaURLFromEnv == currentRMSchemaURLFromEnv)
|
|
)
|
|
$ do
|
|
httpMgr <- askHttpManager
|
|
void $ fetchRemoteSchema env httpMgr name rsi
|
|
|
|
-- This will throw an error if the new schema fetched in incompatible
|
|
-- with the existing permissions and relations
|
|
withNewInconsistentObjsCheck $
|
|
buildSchemaCacheFor (MORemoteSchema name) $
|
|
MetadataModifier $ metaRemoteSchemas %~ OMap.insert name (remoteSchemaMeta metadataRMSchemaPerms)
|
|
|
|
pure successMsg
|
|
where
|
|
remoteSchemaMeta perms = RemoteSchemaMetadata name defn comment perms mempty
|