packages feed

infernal (empty) → 0.1.0

raw patch · 3 files changed

+458/−0 lines, 3 filesdep +aesondep +basedep +binary

Dependencies added: aeson, base, binary, bytestring, case-insensitive, containers, exceptions, heart-app, http-client, http-types, mtl, text, unliftio, unordered-containers, wai

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Eric Conlon (c) 2020++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Eric Conlon nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ infernal.cabal view
@@ -0,0 +1,52 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.31.2.+--+-- see: https://github.com/sol/hpack+--+-- hash: 7f0360108033b54eae7581fe3d7629fac995c43ba61de2280b89dc27bfc36f32++name:           infernal+version:        0.1.0+synopsis:       The Infernal Machine - An AWS Lambda Custom Runtime for Haskell+description:    Please see the README on GitHub at <https://github.com/ejconlon/infernal#readme>+category:       AWS+homepage:       https://github.com/ejconlon/infernal#readme+bug-reports:    https://github.com/ejconlon/infernal/issues+author:         Eric Conlon+maintainer:     ejconlon@gmail.com+copyright:      (c) 2020 Eric Conlon+license:        BSD3+license-file:   LICENSE+build-type:     Simple++source-repository head+  type: git+  location: https://github.com/ejconlon/infernal++library+  exposed-modules:+      Infernal+  other-modules:+      Paths_infernal+  hs-source-dirs:+      src+  default-extensions: ConstraintKinds DeriveFunctor DeriveFoldable DeriveGeneric DeriveTraversable DerivingStrategies GeneralizedNewtypeDeriving NoImplicitPrelude OverloadedStrings TemplateHaskell+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -fno-warn-unused-top-binds+  build-depends:+      aeson >=1.4+    , base >=4.12 && <5+    , binary >=0.8+    , bytestring >=0.10+    , case-insensitive >=1.2+    , containers >=0.6+    , exceptions >=0.10+    , heart-app >=0.1.1+    , http-client >=0.6+    , http-types >=0.12+    , mtl >=2.2+    , text >=1.2+    , unliftio >=0.2+    , unordered-containers >=0.2+    , wai >=3.2+  default-language: Haskell2010
+ src/Infernal.hs view
@@ -0,0 +1,376 @@+{-|+The Infernal Machine - An AWS Lambda Custom Runtime for Haskell++See 'mkMain' or 'mkSimpleMain' for entrypoints to build your Lambda function.+-}+module Infernal+  ( CallbackConfig (..)+  , InitErrorCallback+  , InvokeErrorCallback+  , LambdaError (..)+  , LambdaRequestId (..)+  , LambdaRequest (..)+  , LambdaVars (..)+  , RunCallback+  , UncaughtErrorCallback+  , defaultCallbackConfig+  , defaultLambdaError+  , defaultInitErrorCallback+  , mkMain+  , mkSimpleMain+  ) where++import Control.Monad (forever, void)+import Data.Aeson (encode, pairs, (.=))+import qualified Data.ByteString.Lazy as LBS+import qualified Data.CaseInsensitive as CI+import Data.Maybe (isJust)+import qualified Data.Text as Text+import Data.Text.Encoding (decodeUtf8, encodeUtf8)+import Heart.App.SuperPrelude+import qualified Network.HTTP.Client as HC+import qualified Network.HTTP.Types as HT+import System.Exit (ExitCode)+import System.IO (BufferMode (LineBuffering), hSetBuffering, stderr, stdout)+import Text.Read (readMaybe)+import UnliftIO.Environment (getEnv)++-- TODO This should go in heart-core, along with catch functions+unliftRIO :: MonadIO m => env -> m (UnliftIO (RIO env))+unliftRIO env = liftIO (runRIO env askUnliftIO)++unliftPure :: UnliftIO IO+unliftPure = UnliftIO id++-- | The UUID associated with a Lambda request.+newtype LambdaRequestId = LambdaRequestId+  { _unLambdaRequestId :: Text+  } deriving newtype (Eq, Show, Ord, IsString, Hashable)++-- | The request parsed from the "next invocation" API (<https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html#runtimes-api-next docs>)+data LambdaRequest = LambdaRequest+  { _lreqId :: !LambdaRequestId   -- ^ From the @Lambda-Runtime-Aws-Request-Id@ header+  , _lreqTraceId :: !Text         -- ^ From the @Lambda-Runtime-Trace-Id@ header+  , _lreqFunctionArn :: !Text     -- ^ From the @Lambda-Runtime-Invoked-Function-Arn@ header+  , _lreqDeadlineMs :: !Int       -- ^ From the @Lambda-Runtime-Deadline-Ms@ header, an epoch deadline in ms+  , _lreqBody :: !LBS.ByteString  -- ^ The unparsed request body. Typically you will 'decode' this with Aeson.+  } deriving stock (Eq, Show)++-- | An error formatted to propagate to AWS (<https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html#runtimes-api-invokeerror docs>).+--   Note that this is an 'Exception' so you can throw it to short-circuit processing and report useful information. If you throw anything+--   else a 'defaultLambdaError' will be reported with no useful information.+data LambdaError = LambdaError+  { _lerrErrorType :: !Text     -- ^ The type of error that occurred. In this library is is often @StartCase@-formated.+  , _lerrErrorMessage :: !Text  -- ^ A useful error message+  } deriving stock (Eq, Show, Typeable, Generic)++instance Exception LambdaError++instance ToJSON LambdaError where+  toEncoding (LambdaError ty msg) = pairs ("errorType" .= ty <> "errorMessage" .= msg)++-- | A 'LambdaError' that indicates a vague @InternalError@ to AWS.+defaultLambdaError :: LambdaError+defaultLambdaError = LambdaError "InternalError" "No information is available."++-- | Environment variables set by AWS (<https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime docs>).+--   You may not need to read any of these, but the implementation needs the API endpoint var to handle requests.+data LambdaVars = LambdaVars+  { _lvLogGroupName :: !Text     -- ^ From the @AWS_LAMBDA_LOG_GROUP_NAME@ env var+  , _lvLogStreamName :: !Text    -- ^ From the @AWS_LAMBDA_LOG_STREAM_NAME@ env var+  , _lvFunctionVersion :: !Text  -- ^ From the @AWS_LAMBDA_FUNCTION_VERSION@ env var+  , _lvFunctionName :: !Text     -- ^ From the @AWS_LAMBDA_FUNCTION_NAME@ env var+  , _lvTaskRoot :: !Text         -- ^ From the @LAMBDA_TASK_ROOT@ env var+  , _lvApiEndpoint :: !Text      -- ^ From the @AWS_LAMBDA_RUNTIME_API@ env var+  , _lvFunctionMemory :: !Text   -- ^ From the @AWS_LAMBDA_FUNCTION_MEMORY_SIZE@ env var+  , _lvHandlerName :: !Text      -- ^ From the @_HANDLER@ env var+  }++data LambdaEnv = LambdaEnv+  { _leManager :: !HC.Manager+  , _leVars :: !LambdaVars+  , _leSimpleLog :: !SimpleLogAction+  }++$(makeLenses ''LambdaEnv)++instance HasSimpleLog LambdaEnv where+  simpleLogL = leSimpleLog++class HasLambdaEnv env where+  lambdaEnvL :: Lens' env LambdaEnv++instance HasLambdaEnv LambdaEnv where+  lambdaEnvL = id++type GetLambdaRequest m = m LambdaRequest+type PostLambdaInitError m = LambdaError -> m ()+type PostLambdaInvokeError m = LambdaRequestId -> LambdaError -> m ()+type PostLambdaResponse m = LambdaRequestId -> LBS.ByteString -> m ()++data LambdaClient m = LambdaClient+  { _lcGetLambdaRequest :: !(GetLambdaRequest m)+  , _lcPostLambdaInitError :: !(PostLambdaInitError m)+  , _lcPostLambdaInvokeError :: !(PostLambdaInvokeError m)+  , _lcPostLambdaResponse :: !(PostLambdaResponse m)+  }++-- | Error mapper for init errors. The result will be @POST@ed to the init error endpoint (<https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html#runtimes-api-initerror docs>).+--   Exceptions of type 'LambdaError' will not trigger this callback, and 'System.Exit.ExitCode' will be rethrown after it executes.+type InitErrorCallback n = SomeException -> n LambdaError++-- | A 'InitErrorCallback' that always returns 'defaultLambdaError'.+defaultInitErrorCallback :: Applicative n => InitErrorCallback n+defaultInitErrorCallback _ = pure defaultLambdaError++-- | The "function" part of your Lambda: takes a request with a JSON-encoded body and returns a JSON-encoded response body. You can throw any 'Exception' and the appropriate error+--   callbacks will process it. Most importantly, 'LambdaError' will propagate a formatted error to AWS, and 'System.Exit.ExitCode' will halt the program. Except for 'System.Exit.ExitCode',+--   throwing exceptions here will not terminate the main loop (see 'mkMain'). Note that the AWS custom runtime loop implemented in this library is single-threaded (as required - we must finish an invocation+--   before fetching the next) but you are free to spawn threads in your callback.+type RunCallback n = LambdaRequest -> n LBS.ByteString++-- | Error mapper for invocation errors. The result will be @POST@ed to the invocation error endpoint (<https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html#runtimes-api-invokeerror docs>).+--   Exceptions of type 'LambdaError' will not trigger this callback, and 'System.Exit.ExitCode' will be rethrown after it executes.+type InvokeErrorCallback n = LambdaRequest -> SomeException -> n LambdaError++-- | A handler for otherwise uncaught errors (like failures to fetch next invocation). These happen outside context in which we can report them to AWS, so there is no need to return a 'LambdaError'.+--   Exceptions of type 'System.Exit.ExitCode' will be rethrown after this callback executes.+type UncaughtErrorCallback n = SomeException -> n ()++-- | User callbacks to be invoked by the main loop (see 'mkMain').+data CallbackConfig n = CallbackConfig+  { _cbcRunCallback :: !(RunCallback n)                       -- ^ See 'RunCallback'+  , _cbcInvokeErrorCallback :: !(InvokeErrorCallback n)       -- ^ See 'InvokeErrorCallback'+  , _cbcUncaughtErrorCallback :: !(UncaughtErrorCallback n)   -- ^ See 'UncaughtErrorCallback'+  }++-- | A 'CallbackConfig' that returns 'defaultLamdaError' from all error callbacks.+defaultCallbackConfig :: Applicative n => RunCallback n -> CallbackConfig n+defaultCallbackConfig runCb = CallbackConfig runCb (\_ _ -> pure defaultLambdaError) (\_ -> pure ())++unliftInto :: MonadIO m => UnliftIO n -> n a -> m a+unliftInto (UnliftIO runIO) n = liftIO (runIO n)++castLambdaError :: SomeException -> Maybe LambdaError+castLambdaError = fromException++castExitCode :: SomeException -> Maybe ExitCode+castExitCode = fromException++catchRethrowWhen :: MonadCatch m => (SomeException -> Bool) -> m a -> (SomeException -> m a) -> m a+catchRethrowWhen predicate act handler = catch act (\err -> handler err >>= if predicate err then const (throwM err) else pure)++catchRethrow :: MonadCatch m => m a -> (SomeException -> m ()) -> m a+catchRethrow act handler = catch act (\err -> handler err >> throwM err)++catchRethrowExitCode :: MonadCatch m => m a -> (SomeException -> m a) -> m a+catchRethrowExitCode = catchRethrowWhen (isJust . castExitCode)++handleInvokeError :: WithSimpleLog env m => PostLambdaInvokeError m -> UnliftIO n -> InvokeErrorCallback n -> LambdaRequest -> SomeException -> m ()+handleInvokeError postErr unio invokeErrCb lamReq err = do+  let lamIdText = _unLambdaRequestId (_lreqId lamReq)+  logError ("Caught invocation error for request id " <> lamIdText <> ":")+  logException err+  lamErr <- case castLambdaError err of+    Just lamErr -> do+      logError ("Posting original invocation error for request id " <> lamIdText)+      pure lamErr+    Nothing -> do+      lamErr <- unliftInto unio (invokeErrCb lamReq err)+      logError ("Posting new invocation error for request id " <> lamIdText <> ":")+      logException lamErr+      pure lamErr+  postErr (_lreqId lamReq) lamErr++guardInvokeError :: (MonadCatch m, WithSimpleLog env m) => PostLambdaInvokeError m -> UnliftIO n -> InvokeErrorCallback n -> LambdaRequest -> m () -> m ()+guardInvokeError postErr unio invokeErrCb lamReq body = catchRethrowExitCode body (handleInvokeError postErr unio invokeErrCb lamReq)++missedDeadlineError :: Int -> LambdaError+missedDeadlineError deadlineMs = LambdaError "MissedDeadlineError" ("Missed adjusted deadline of " <> Text.pack (show deadlineMs) <> "ms.")++pollAndRespond :: (MonadCatch m, WithSimpleLog env m) => LambdaClient m -> UnliftIO n -> CallbackConfig n -> m ()+pollAndRespond client unio cbc = do+  let runCb = _cbcRunCallback cbc+      invokeErrCb = _cbcInvokeErrorCallback cbc+      postErr = _lcPostLambdaInvokeError client+      postRep = _lcPostLambdaResponse client+  logDebug "Polling for request"+  lamReq <- _lcGetLambdaRequest client+  let lamReqId = _lreqId lamReq+      lamIdText = _unLambdaRequestId lamReqId+  logDebug ("Servicing request id " <> lamIdText)+  guardInvokeError postErr unio invokeErrCb lamReq $ do+    lamRepBody <- unliftInto unio (runCb lamReq)+    logDebug ("Posting response to request id " <> lamIdText)+    postRep lamReqId lamRepBody+    logDebug ("Finished request id " <> lamIdText)++pollLoop :: (MonadCatch m, WithSimpleLog env m) => LambdaClient m -> UnliftIO n -> CallbackConfig n -> m ()+pollLoop client unio cbc =+  let uncaughtErrCb = _cbcUncaughtErrorCallback cbc+  in forever $ catchRethrowExitCode (pollAndRespond client unio cbc) $ \err -> do+    logError "Handling uncaught error:"+    logException err+    unliftInto unio (uncaughtErrCb err)++getEnvText :: (MonadThrow m, MonadIO m) => Text -> m Text+getEnvText = fmap Text.pack . getEnv . Text.unpack++readLambdaVars :: (MonadThrow m, MonadIO m) => m LambdaVars+readLambdaVars = do+  logGroupName <- getEnvText "AWS_LAMBDA_LOG_GROUP_NAME"+  logStreamName <- getEnvText "AWS_LAMBDA_LOG_STREAM_NAME"+  functionVersion <- getEnvText "AWS_LAMBDA_FUNCTION_VERSION"+  functionName <- getEnvText "AWS_LAMBDA_FUNCTION_NAME"+  taskRoot <- getEnvText "LAMBDA_TASK_ROOT"+  apiEndpoint <- getEnvText "AWS_LAMBDA_RUNTIME_API"+  functionMemory <- getEnvText "AWS_LAMBDA_FUNCTION_MEMORY_SIZE"+  handlerName <- getEnvText "_HANDLER"+  pure LambdaVars+    { _lvLogGroupName = logGroupName+    , _lvLogStreamName = logStreamName+    , _lvFunctionVersion = functionVersion+    , _lvFunctionName = functionName+    , _lvTaskRoot = taskRoot+    , _lvApiEndpoint = apiEndpoint+    , _lvFunctionMemory = functionMemory+    , _lvHandlerName = handlerName+    }++newLambdaEnv :: (MonadThrow m, WithSimpleLog env m) => m LambdaEnv+newLambdaEnv = do+  manager <- liftIO (HC.newManager httpManagerSettings)+  vars <- readLambdaVars+  simpleLog <- view simpleLogL+  pure (LambdaEnv manager vars simpleLog)++type MonadLambdaImpl env m = (MonadIO m, MonadThrow m, MonadReader env m, HasLambdaEnv env)++askApiEndpoint :: MonadLambdaImpl env m => m Text+askApiEndpoint = do+  lambdaEnv <- view lambdaEnvL+  pure (_lvApiEndpoint (_leVars lambdaEnv))++askManager :: MonadLambdaImpl env m => m HC.Manager+askManager = do+  lambdaEnv <- view lambdaEnvL+  pure (_leManager lambdaEnv)++baseUrl :: MonadLambdaImpl env m => m [Text]+baseUrl = do+  apiEndpoint <- askApiEndpoint+  pure [apiEndpoint, "2018-06-01", "runtime"]++formatUrl :: MonadLambdaImpl env m => [Text] -> m Text+formatUrl suffix = do+  prefix <- baseUrl+  let parts = prefix <> suffix+  pure (Text.intercalate "/" ("http:/" : parts))++initRequest :: MonadLambdaImpl env m => [Text] -> m HC.Request+initRequest suffix = do+  url <- formatUrl suffix+  HC.parseUrlThrow (Text.unpack url)++missingHeaderError :: Text -> LambdaError+missingHeaderError name = LambdaError "MissingHeaderError" ("Missing header " <> name <> " in next invocation request.")++invalidDeadlineError :: Text -> LambdaError+invalidDeadlineError deadlineMsText = LambdaError "InvalidDeadlineError" ("Invalid value for deadline: " <> deadlineMsText)++lookupHeader :: MonadThrow m => Text -> HT.ResponseHeaders -> m Text+lookupHeader name headers =+  case lookup (CI.mk (encodeUtf8 name)) headers of+    Nothing -> throwM (missingHeaderError name)+    Just value -> pure (decodeUtf8 value)++parseLambdaRequest :: MonadThrow m => HT.ResponseHeaders -> LBS.ByteString -> m LambdaRequest+parseLambdaRequest headers body = do+  reqId <- lookupHeader "Lambda-Runtime-Aws-Request-Id" headers+  traceId <- lookupHeader "Lambda-Runtime-Trace-Id" headers+  functionArn <- lookupHeader "Lambda-Runtime-Invoked-Function-Arn" headers+  deadlineMsText <- lookupHeader "Lambda-Runtime-Deadline-Ms" headers+  deadlineMs <- case readMaybe (Text.unpack deadlineMsText) of+    Just value -> pure value+    Nothing -> throwM (invalidDeadlineError deadlineMsText)+  pure $ LambdaRequest+    { _lreqId = LambdaRequestId reqId+    , _lreqTraceId = traceId+    , _lreqFunctionArn = functionArn+    , _lreqDeadlineMs = deadlineMs+    , _lreqBody = body+    }++getLambdaRequestImpl :: MonadLambdaImpl env m => GetLambdaRequest m+getLambdaRequestImpl = do+  manager <- askManager+  rawRequest <- initRequest ["invocation", "next"]+  let request = rawRequest { HC.method = "GET" }+  response <- liftIO (HC.httpLbs request manager)+  let headers = HC.responseHeaders response+  let body = HC.responseBody response+  parseLambdaRequest headers body++postRequest :: MonadLambdaImpl env m => [Text] -> LBS.ByteString -> m ()+postRequest suffix body = do+  manager <- askManager+  rawRequest <- initRequest suffix+  let request = rawRequest { HC.method = "POST", HC.requestBody = HC.RequestBodyLBS body }+  void (liftIO (HC.httpNoBody request manager))++postLambdaInitErrorImpl :: MonadLambdaImpl env m => PostLambdaInitError m+postLambdaInitErrorImpl = postRequest ["init", "error"] . encode++postLambdaInvokeErrorImpl :: MonadLambdaImpl env m => PostLambdaInvokeError m+postLambdaInvokeErrorImpl lamReqId = postRequest ["invocation", _unLambdaRequestId lamReqId, "error"] . encode++postLambdaResponseImpl :: MonadLambdaImpl env m => PostLambdaResponse m+postLambdaResponseImpl lamReqId = postRequest ["invocation", _unLambdaRequestId lamReqId, "response"]++lambdaClientImpl :: MonadLambdaImpl env m => LambdaClient m+lambdaClientImpl = LambdaClient+  { _lcGetLambdaRequest = getLambdaRequestImpl+  , _lcPostLambdaInitError = postLambdaInitErrorImpl+  , _lcPostLambdaInvokeError = postLambdaInvokeErrorImpl+  , _lcPostLambdaResponse = postLambdaResponseImpl+  }++httpManagerSettings :: HC.ManagerSettings+httpManagerSettings = HC.defaultManagerSettings { HC.managerResponseTimeout = HC.responseTimeoutNone }++-- | The full-powered entrypoint underlying 'mkSimpleMain' that allows you to use any 'UnliftIO'-capable monad for your callbacks.+--   This runs the main loop of our AWS Lambda Custom Runtime to fetch invocations, process them, and report errors or results.+--   Control will not return from this function, and AWS Lambda will terminate the process at its will.+mkMain ::+  (MonadCatch m, WithSimpleLog env m)+  => UnliftIO n                            -- ^ Runs your monad @n@ in IO (see @MonadUnliftIO@ from @unliftio-core@)+  -> InitErrorCallback n                   -- ^ Error mapper for the callback builder+  -> (LambdaVars -> n (CallbackConfig n))  -- ^ Callback builder. When possible, do init work here so the framework can propagate init errors to AWS.+  -> m ()+mkMain unio initErrCb cbcInit = do+  logDebug "Initializing lambda environment"+  lambdaEnv <- newLambdaEnv+  runRIO lambdaEnv $ do+    let client = lambdaClientImpl+        lamVars = _leVars lambdaEnv+    logDebug "Initializing callbacks"+    cbc <- catchRethrow (unliftInto unio (cbcInit lamVars)) $ \err -> do+      logError "Caught initialization error:"+      logException err+      lamErr <- unliftInto unio (initErrCb err)+      logError "Posting mapped initialization error:"+      logException lamErr+      _lcPostLambdaInitError client lamErr+    logDebug "Starting poll loop"+    pollLoop client unio cbc++-- | A simple entrypoint that delegates to 'mkMain'. Use this as the body of your @main@ function if you want to get a Lambda function up and running quickly.+--   All you need to do is provide a 'RunCallback' that handles JSON-encoded requests and returns JSON-encoded responses (or throws 'LambdaError' exceptions).+--   Your callback has access to a simple logger (try 'logInfo', for example) whose output will be collected by Lambda and published to CloudWatch.+mkSimpleMain :: RunCallback (RIO App) -> IO ()+mkSimpleMain cb = do+  hSetBuffering stdout LineBuffering+  hSetBuffering stderr LineBuffering+  app <- newApp+  unio <- unliftRIO app+  runRIO app (mkMain unio defaultInitErrorCallback (const (pure (defaultCallbackConfig cb))))