From 298a34f3ab267ddb835706bced569aa2ec945110 Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Tue, 18 Jun 2019 19:31:43 +0100 Subject: [PATCH 1/7] Implement single process --- app/Main.hs | 14 +----- package.yaml | 2 +- src/Aws/Lambda.hs | 1 + src/Aws/Lambda/Configuration.hs | 13 ++++- src/Aws/Lambda/Meta/Common.hs | 5 ++ src/Aws/Lambda/Meta/DispatchNoIPC.hs | 73 ++++++++++++++++++++++++++++ src/Aws/Lambda/Meta/Main.hs | 30 ++++++------ src/Aws/Lambda/Meta/Run.hs | 9 ++++ src/Aws/Lambda/Runtime.hs | 71 +++++++++++++++++++++------ src/Aws/Lambda/Runtime/Common.hs | 35 +++++++++++++ src/Aws/Lambda/Runtime/IPC.hs | 2 +- src/Aws/Lambda/Runtime/Publish.hs | 2 +- src/Aws/Lambda/Runtime/Result.hs | 7 --- 13 files changed, 210 insertions(+), 54 deletions(-) create mode 100644 src/Aws/Lambda/Meta/DispatchNoIPC.hs create mode 100644 src/Aws/Lambda/Runtime/Common.hs delete mode 100644 src/Aws/Lambda/Runtime/Result.hs diff --git a/app/Main.hs b/app/Main.hs index cb0a53f..3e2a746 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -4,18 +4,6 @@ module Main ) where import Aws.Lambda.Runtime -import Control.Monad -import qualified Network.HTTP.Client as Http - - -httpManagerSettings :: Http.ManagerSettings -httpManagerSettings = - -- We set the timeout to none, as AWS Lambda freezes the containers. - Http.defaultManagerSettings - { Http.managerResponseTimeout = Http.responseTimeoutNone - } main :: IO () -main = do - manager <- Http.newManager httpManagerSettings - forever (runLambda manager) +main = runLambda IPC diff --git a/package.yaml b/package.yaml index 997ff84..ad5f55b 100644 --- a/package.yaml +++ b/package.yaml @@ -37,7 +37,7 @@ library: - Aws.Lambda.Runtime executables: - bootstrap: + bootstrap-lol: source-dirs: app main: Main.hs dependencies: diff --git a/src/Aws/Lambda.hs b/src/Aws/Lambda.hs index 3b6b9e6..d495fae 100644 --- a/src/Aws/Lambda.hs +++ b/src/Aws/Lambda.hs @@ -3,4 +3,5 @@ module Aws.Lambda ) where import Aws.Lambda.Configuration as Reexported +import Aws.Lambda.Runtime as Reexported import Aws.Lambda.Runtime.Context as Reexported diff --git a/src/Aws/Lambda/Configuration.hs b/src/Aws/Lambda/Configuration.hs index 38bf4d1..1ce802a 100644 --- a/src/Aws/Lambda/Configuration.hs +++ b/src/Aws/Lambda/Configuration.hs @@ -1,18 +1,20 @@ {-# OPTIONS_GHC -fno-warn-unused-pattern-binds #-} module Aws.Lambda.Configuration ( Main.LambdaOptions(..) - , Main.generate , Main.getRecord , configureLambda + , bootstrapLambda , IPC.returnAndFail , IPC.returnAndSucceed , Dispatch.decodeObj + , DispatchNoIPC.encodeObj ) where import qualified Language.Haskell.TH as Meta import qualified Aws.Lambda.Meta.Dispatch as Dispatch +import qualified Aws.Lambda.Meta.DispatchNoIPC as DispatchNoIPC import qualified Aws.Lambda.Meta.Main as Main import qualified Aws.Lambda.Meta.Run as Run import qualified Aws.Lambda.Runtime.IPC as IPC @@ -22,6 +24,13 @@ AWS Lambda layer. -} configureLambda :: Meta.DecsQ configureLambda = do - main <- Main.generate + main <- Main.generateIPC run <- Run.generate return (main <> [run]) + +{-| -} +bootstrapLambda :: Meta.DecsQ +bootstrapLambda = do + main <- Main.generateDirectCall + run <- Run.generateNoIPC + return (main <> [run]) \ No newline at end of file diff --git a/src/Aws/Lambda/Meta/Common.hs b/src/Aws/Lambda/Meta/Common.hs index bae9c3f..25c7b9b 100644 --- a/src/Aws/Lambda/Meta/Common.hs +++ b/src/Aws/Lambda/Meta/Common.hs @@ -3,6 +3,7 @@ module Aws.Lambda.Meta.Common ( declarationName , expressionName , getFieldsFrom + , constructorName ) where import Data.Text (Text) @@ -19,6 +20,10 @@ declarationName = pure . VarP . mkName . Text.unpack expressionName :: Text -> Q Exp expressionName = pure . VarE . mkName . Text.unpack +-- | Helper for defining names for constructors +-- think of @Foo@ in @quux = Foo 3@ +constructorName :: Text -> Q Exp +constructorName = pure . ConE . mkName . Text.unpack -- | Helper for extracting fields of a specified record -- it expects the constructor name as the first parameter, diff --git a/src/Aws/Lambda/Meta/DispatchNoIPC.hs b/src/Aws/Lambda/Meta/DispatchNoIPC.hs new file mode 100644 index 0000000..7d2e475 --- /dev/null +++ b/src/Aws/Lambda/Meta/DispatchNoIPC.hs @@ -0,0 +1,73 @@ +{-| Dispatcher generation -} +module Aws.Lambda.Meta.DispatchNoIPC + ( generate + , decodeObj + , encodeObj + , Runtime.LambdaResult(..) + ) where + +import Data.Function ((&)) +import Data.Text (Text) +import qualified Data.Text as Text + +import Data.Aeson +import qualified Data.ByteString.Lazy.Char8 as LazyByteString +import qualified Language.Haskell.TH as Meta + +import Aws.Lambda.Meta.Common +import qualified Aws.Lambda.Runtime.Common as Runtime + +{-| Helper function that the dispatcher will use to +decode the JSON that comes as an AWS Lambda event into the +appropriate type expected by the handler. +-} +decodeObj :: FromJSON a => String -> a +decodeObj x = + case (eitherDecode $ LazyByteString.pack x) of + Left e -> error e + Right v -> v + +{-| Helper function that the dispatcher will use to +decode the JSON that comes as an AWS Lambda event into the +appropriate type expected by the handler. +-} +encodeObj :: ToJSON a => a -> String +encodeObj x = LazyByteString.unpack (encode x) + + +{-| Generates the dispatcher out of a list of +handler names in the form @src/Foo/Bar.handler@ + +This dispatcher has a case for each of the handlers that calls +the appropriate qualified function. In the case of the example above, +the dispatcher will call @Foo.Bar.handler@. +-} +generate :: [Text] -> Meta.ExpQ +generate handlerNames = do + caseExp <- expressionName "functionHandler" + matches <- traverse handlerCase handlerNames + unmatched <- unmatchedCase + pure $ Meta.CaseE caseExp (matches <> [unmatched]) + +handlerCase :: Text -> Meta.MatchQ +handlerCase lambdaHandler = do + let pat = Meta.LitP (Meta.StringL $ Text.unpack lambdaHandler) + body <- [e|do + result <- $(expressionName qualifiedName) (decodeObj $(expressionName "eventObject")) (decodeObj $(expressionName "contextObject")) +-- ($(constructorName "LambdaResult") . encodeObj) + either (pure . Left . encodeObj) (pure . Right . $(constructorName "LambdaResult") . encodeObj) result |] + pure $ Meta.Match pat (Meta.NormalB body) [] + where + qualifiedName = + lambdaHandler + & Text.dropWhile (/= '/') + & Text.drop 1 + & Text.replace "/" "." + +unmatchedCase :: Meta.MatchQ +unmatchedCase = do + let pattern = Meta.WildP + body <- [e| + pure $ Left ("Handler " <> $(expressionName "functionHandler") <> " does not exist on project") + |] + pure $ Meta.Match pattern (Meta.NormalB body) [] diff --git a/src/Aws/Lambda/Meta/Main.hs b/src/Aws/Lambda/Meta/Main.hs index 7f64e78..78d5746 100644 --- a/src/Aws/Lambda/Meta/Main.hs +++ b/src/Aws/Lambda/Meta/Main.hs @@ -1,27 +1,29 @@ {-| main function generation for interoperation with the layer -} module Aws.Lambda.Meta.Main - ( LambdaOptions(..) - , generate + ( Runtime.LambdaOptions(..) + , generateIPC + , generateDirectCall , Options.getRecord ) where -import GHC.Generics (Generic) - import qualified Language.Haskell.TH as Meta import qualified Options.Generic as Options import Aws.Lambda.Meta.Common - --- | Options that the generated main expects -data LambdaOptions = LambdaOptions - { eventObject :: !String - , contextObject :: !String - , functionHandler :: !String - , executionUuid :: !String - } deriving (Generic, Options.ParseRecord) +import qualified Aws.Lambda.Runtime.Common as Runtime -- | Generate the main function that the layer will call -generate :: Meta.DecsQ -generate = [d| +generateIPC :: Meta.DecsQ +generateIPC = [d| $(declarationName "main") = getRecord "" >>= run |] + +generateDirectCall :: Meta.DecsQ +generateDirectCall = [d| + $(declarationName "main") = $(directCallBody) + |] + where + directCallBody = + [e|do + runLambda $ $(constructorName "DirectCall") run + |] diff --git a/src/Aws/Lambda/Meta/Run.hs b/src/Aws/Lambda/Meta/Run.hs index 7277add..2772b08 100644 --- a/src/Aws/Lambda/Meta/Run.hs +++ b/src/Aws/Lambda/Meta/Run.hs @@ -1,5 +1,6 @@ module Aws.Lambda.Meta.Run ( generate + , generateNoIPC ) where import qualified Language.Haskell.TH as Meta @@ -7,6 +8,7 @@ import qualified Language.Haskell.TH as Meta import Aws.Lambda.Meta.Common import qualified Aws.Lambda.Meta.Discover as Discover import qualified Aws.Lambda.Meta.Dispatch as Dispatch +import qualified Aws.Lambda.Meta.DispatchNoIPC as DispatchNoIPC {-| Generate the run function @@ -20,3 +22,10 @@ generate = do clause' <- getFieldsFrom "LambdaOptions" ["functionHandler", "contextObject", "eventObject", "executionUuid"] body <- Dispatch.generate handlers pure $ Meta.FunD (Meta.mkName "run") [Meta.Clause [clause'] (Meta.NormalB body) []] + +generateNoIPC :: Meta.DecQ +generateNoIPC = do + handlers <- Meta.runIO Discover.handlers + clause' <- getFieldsFrom "LambdaOptions" ["functionHandler", "contextObject", "eventObject", "executionUuid"] + body <- DispatchNoIPC.generate handlers + pure $ Meta.FunD (Meta.mkName "run") [Meta.Clause [clause'] (Meta.NormalB body) []] diff --git a/src/Aws/Lambda/Runtime.hs b/src/Aws/Lambda/Runtime.hs index 9922ece..a72e98f 100644 --- a/src/Aws/Lambda/Runtime.hs +++ b/src/Aws/Lambda/Runtime.hs @@ -1,45 +1,86 @@ module Aws.Lambda.Runtime ( runLambda + , Runtime.Mode(..) + , Runtime.LambdaResult(..) ) where import Control.Exception.Safe.Checked +import Control.Monad (forever) import qualified Network.HTTP.Client as Http +import Data.Aeson +import qualified Data.ByteString.Lazy.Char8 as LazyByteString + import qualified Aws.Lambda.Runtime.ApiInfo as ApiInfo import qualified Aws.Lambda.Runtime.Context as Context import qualified Aws.Lambda.Runtime.Environment as Environment import qualified Aws.Lambda.Runtime.Error as Error import qualified Aws.Lambda.Runtime.IPC as IPC import qualified Aws.Lambda.Runtime.Publish as Publish +import qualified Aws.Lambda.Runtime.Common as Runtime -- | Runs the user @haskell_lambda@ executable and posts back the --- results -runLambda - :: Http.Manager - -> IO () -runLambda manager = do - lambdaApi <- Environment.apiEndpoint `catch` variableNotSet - event <- ApiInfo.fetchEvent manager lambdaApi `catch` errorParsing - context <- Context.initialize event `catch` errorParsing `catch` variableNotSet - ((invokeAndRun manager lambdaApi event context - `catch` \err -> Publish.parsingError err lambdaApi context manager) - `catch` \err -> Publish.invocationError err lambdaApi context manager) - `catch` \(err :: Error.EnvironmentVariableNotSet) -> Publish.runtimeInitError err lambdaApi context manager +-- results. This is called from the layer's @main@ function. +runLambda :: Runtime.Mode -> IO () +runLambda mode = do + manager <- Http.newManager httpManagerSettings + forever $ do + lambdaApi <- Environment.apiEndpoint `catch` variableNotSet + event <- ApiInfo.fetchEvent manager lambdaApi `catch` errorParsing + context <- Context.initialize event `catch` errorParsing `catch` variableNotSet + ((invokeAndRun mode manager lambdaApi event context + `catch` \err -> Publish.parsingError err lambdaApi context manager) + `catch` \err -> Publish.invocationError err lambdaApi context manager) + `catch` \(err :: Error.EnvironmentVariableNotSet) -> Publish.runtimeInitError err lambdaApi context manager + +httpManagerSettings :: Http.ManagerSettings +httpManagerSettings = + -- We set the timeout to none, as AWS Lambda freezes the containers. + Http.defaultManagerSettings + { Http.managerResponseTimeout = Http.responseTimeoutNone + } invokeAndRun :: Throws Error.Parsing => Throws Error.Invocation => Throws Error.EnvironmentVariableNotSet - => Http.Manager + => Runtime.Mode + -> Http.Manager -> String -> ApiInfo.Event -> Context.Context -> IO () -invokeAndRun manager lambdaApi event context = do - result <- IPC.invoke (ApiInfo.event event) context +invokeAndRun mode manager lambdaApi event context = do + result <- invokeWithMode mode event context Publish.result result lambdaApi context manager `catch` \err -> Publish.invocationError err lambdaApi context manager +invokeWithMode + :: Throws Error.Invocation + => Throws Error.Parsing + => Throws Error.EnvironmentVariableNotSet + => Runtime.Mode + -> ApiInfo.Event + -> Context.Context + -> IO Runtime.LambdaResult +invokeWithMode mode event context = + case mode of + Runtime.IPC -> IPC.invoke (ApiInfo.event event) context + (Runtime.DirectCall f) -> do + handlerName <- Environment.handlerName + let lambdaOptions = Runtime.LambdaOptions + { eventObject = LazyByteString.unpack $ ApiInfo.event event + , contextObject = LazyByteString.unpack . encode $ context + , functionHandler = handlerName + , executionUuid = "" -- DirectCall doesnt use UUID + } + result <- f lambdaOptions + case result of + Left err -> + throw $ Error.Invocation err + Right value -> + pure value + variableNotSet :: Error.EnvironmentVariableNotSet -> IO a variableNotSet (Error.EnvironmentVariableNotSet env) = error ("Error initializing, variable not set: " <> env) diff --git a/src/Aws/Lambda/Runtime/Common.hs b/src/Aws/Lambda/Runtime/Common.hs new file mode 100644 index 0000000..f038111 --- /dev/null +++ b/src/Aws/Lambda/Runtime/Common.hs @@ -0,0 +1,35 @@ +module Aws.Lambda.Runtime.Common + ( Mode(..) + , LambdaResult(..) + , LambdaOptions(..) + ) where + +import GHC.Generics (Generic) +import qualified Options.Generic as Options + +{-| Mode of calling the user functions. + +It can be 'IPC' (inter-process communication), where the +dispatcher will spawn a process with the handlers of the +user. (Used when using the layer) + +Or, it can be 'DirectCall', for when the handlers are in +the same process. (The runtime is bootstrapped with the +project). +-} +data Mode + = IPC + | DirectCall (LambdaOptions -> IO (Either String LambdaResult)) + -- ^ This horrible signature implies the following + +-- | Options that the generated main expects +data LambdaOptions = LambdaOptions + { eventObject :: !String + , contextObject :: !String + , functionHandler :: !String + , executionUuid :: !String + } deriving (Generic, Options.ParseRecord) + +-- | Wrapper type to handle the result of the user +newtype LambdaResult = + LambdaResult String \ No newline at end of file diff --git a/src/Aws/Lambda/Runtime/IPC.hs b/src/Aws/Lambda/Runtime/IPC.hs index 5fcb7e5..c58cce3 100644 --- a/src/Aws/Lambda/Runtime/IPC.hs +++ b/src/Aws/Lambda/Runtime/IPC.hs @@ -36,7 +36,7 @@ import qualified Data.UUID.V4 as UUID import Aws.Lambda.Runtime.Context (Context (..)) import qualified Aws.Lambda.Runtime.Environment as Environment import qualified Aws.Lambda.Runtime.Error as Error -import Aws.Lambda.Runtime.Result (LambdaResult (..)) +import Aws.Lambda.Runtime.Common -- | Returns the JSON value failing, according to the protocol returnAndFail :: ToJSON a => String -> a -> IO () diff --git a/src/Aws/Lambda/Runtime/Publish.hs b/src/Aws/Lambda/Runtime/Publish.hs index f56508a..bc302cf 100644 --- a/src/Aws/Lambda/Runtime/Publish.hs +++ b/src/Aws/Lambda/Runtime/Publish.hs @@ -15,7 +15,7 @@ import qualified Network.HTTP.Client as Http import qualified Aws.Lambda.Runtime.API.Endpoints as Endpoints import Aws.Lambda.Runtime.Context (Context (..)) import qualified Aws.Lambda.Runtime.Error as Error -import Aws.Lambda.Runtime.Result (LambdaResult (..)) +import Aws.Lambda.Runtime.Common -- | Publishes the result back to AWS Lambda result :: LambdaResult -> String -> Context -> Http.Manager -> IO () diff --git a/src/Aws/Lambda/Runtime/Result.hs b/src/Aws/Lambda/Runtime/Result.hs deleted file mode 100644 index 466189b..0000000 --- a/src/Aws/Lambda/Runtime/Result.hs +++ /dev/null @@ -1,7 +0,0 @@ -module Aws.Lambda.Runtime.Result - ( LambdaResult(..) - ) where - --- | Wrapper type to handle the result of the user -newtype LambdaResult = - LambdaResult String From 404e0d25cedfda0403d36959b104bb9a39ec5671 Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Wed, 19 Jun 2019 17:28:42 +0100 Subject: [PATCH 2/7] Remove IPC --- app/Main.hs | 9 --- package.yaml | 13 +-- src/Aws/Lambda/Configuration.hs | 20 +---- src/Aws/Lambda/Meta/Dispatch.hs | 15 +++- src/Aws/Lambda/Meta/DispatchNoIPC.hs | 73 ----------------- src/Aws/Lambda/Meta/Main.hs | 18 ++--- src/Aws/Lambda/Meta/Run.hs | 15 ---- src/Aws/Lambda/Runtime.hs | 51 +++++------- src/Aws/Lambda/Runtime/Common.hs | 22 ++--- src/Aws/Lambda/Runtime/IPC.hs | 116 --------------------------- 10 files changed, 49 insertions(+), 303 deletions(-) delete mode 100644 app/Main.hs delete mode 100644 src/Aws/Lambda/Meta/DispatchNoIPC.hs delete mode 100644 src/Aws/Lambda/Runtime/IPC.hs diff --git a/app/Main.hs b/app/Main.hs deleted file mode 100644 index 3e2a746..0000000 --- a/app/Main.hs +++ /dev/null @@ -1,9 +0,0 @@ --- | Main entry point for the layer -module Main - ( main - ) where - -import Aws.Lambda.Runtime - -main :: IO () -main = runLambda IPC diff --git a/package.yaml b/package.yaml index ad5f55b..908ba0f 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: aws-lambda-haskell-runtime -version: 1.1.1 +version: 2.0.0 github: "theam/aws-lambda-haskell-runtime" license: Apache-2.0 author: Nikita Tchayka @@ -23,11 +23,8 @@ library: - bytestring - http-client - http-types - - optparse-generic - - process - template-haskell - text - - uuid - safe-exceptions-checked - path - path-io @@ -36,14 +33,6 @@ library: - Aws.Lambda - Aws.Lambda.Runtime -executables: - bootstrap-lol: - source-dirs: app - main: Main.hs - dependencies: - - aws-lambda-haskell-runtime - - http-client - tests: aws-lambda-haskell-runtime-test: main: Spec.hs diff --git a/src/Aws/Lambda/Configuration.hs b/src/Aws/Lambda/Configuration.hs index 1ce802a..8ec3326 100644 --- a/src/Aws/Lambda/Configuration.hs +++ b/src/Aws/Lambda/Configuration.hs @@ -1,36 +1,22 @@ {-# OPTIONS_GHC -fno-warn-unused-pattern-binds #-} module Aws.Lambda.Configuration ( Main.LambdaOptions(..) - , Main.getRecord , configureLambda - , bootstrapLambda - , IPC.returnAndFail - , IPC.returnAndSucceed , Dispatch.decodeObj - , DispatchNoIPC.encodeObj + , Dispatch.encodeObj ) where import qualified Language.Haskell.TH as Meta import qualified Aws.Lambda.Meta.Dispatch as Dispatch -import qualified Aws.Lambda.Meta.DispatchNoIPC as DispatchNoIPC import qualified Aws.Lambda.Meta.Main as Main import qualified Aws.Lambda.Meta.Run as Run -import qualified Aws.Lambda.Runtime.IPC as IPC -{-| Generates a @main@ function to be used with the -AWS Lambda layer. +{-| Generates a @main@ function that acts as a dispatcher -} configureLambda :: Meta.DecsQ configureLambda = do - main <- Main.generateIPC + main <- Main.generate run <- Run.generate return (main <> [run]) - -{-| -} -bootstrapLambda :: Meta.DecsQ -bootstrapLambda = do - main <- Main.generateDirectCall - run <- Run.generateNoIPC - return (main <> [run]) \ No newline at end of file diff --git a/src/Aws/Lambda/Meta/Dispatch.hs b/src/Aws/Lambda/Meta/Dispatch.hs index d69a1a1..9bf36a8 100644 --- a/src/Aws/Lambda/Meta/Dispatch.hs +++ b/src/Aws/Lambda/Meta/Dispatch.hs @@ -2,6 +2,8 @@ module Aws.Lambda.Meta.Dispatch ( generate , decodeObj + , encodeObj + , Runtime.LambdaResult(..) ) where import Data.Function ((&)) @@ -13,6 +15,7 @@ import qualified Data.ByteString.Lazy.Char8 as LazyByteString import qualified Language.Haskell.TH as Meta import Aws.Lambda.Meta.Common +import qualified Aws.Lambda.Runtime.Common as Runtime {-| Helper function that the dispatcher will use to decode the JSON that comes as an AWS Lambda event into the @@ -24,6 +27,14 @@ decodeObj x = Left e -> error e Right v -> v +{-| Helper function that the dispatcher will use to +decode the JSON that comes as an AWS Lambda event into the +appropriate type expected by the handler. +-} +encodeObj :: ToJSON a => a -> String +encodeObj x = LazyByteString.unpack (encode x) + + {-| Generates the dispatcher out of a list of handler names in the form @src/Foo/Bar.handler@ @@ -43,7 +54,7 @@ handlerCase lambdaHandler = do let pat = Meta.LitP (Meta.StringL $ Text.unpack lambdaHandler) body <- [e|do result <- $(expressionName qualifiedName) (decodeObj $(expressionName "eventObject")) (decodeObj $(expressionName "contextObject")) - either (returnAndFail $(expressionName "executionUuid")) (returnAndSucceed $(expressionName "executionUuid")) result |] + either (pure . Left . encodeObj) (pure . Right . $(constructorName "LambdaResult") . encodeObj) result |] pure $ Meta.Match pat (Meta.NormalB body) [] where qualifiedName = @@ -56,6 +67,6 @@ unmatchedCase :: Meta.MatchQ unmatchedCase = do let pattern = Meta.WildP body <- [e| - returnAndFail $(expressionName "executionUuid") ("Handler " <> $(expressionName "functionHandler") <> " does not exist on project") + pure $ Left ("Handler " <> $(expressionName "functionHandler") <> " does not exist on project") |] pure $ Meta.Match pattern (Meta.NormalB body) [] diff --git a/src/Aws/Lambda/Meta/DispatchNoIPC.hs b/src/Aws/Lambda/Meta/DispatchNoIPC.hs deleted file mode 100644 index 7d2e475..0000000 --- a/src/Aws/Lambda/Meta/DispatchNoIPC.hs +++ /dev/null @@ -1,73 +0,0 @@ -{-| Dispatcher generation -} -module Aws.Lambda.Meta.DispatchNoIPC - ( generate - , decodeObj - , encodeObj - , Runtime.LambdaResult(..) - ) where - -import Data.Function ((&)) -import Data.Text (Text) -import qualified Data.Text as Text - -import Data.Aeson -import qualified Data.ByteString.Lazy.Char8 as LazyByteString -import qualified Language.Haskell.TH as Meta - -import Aws.Lambda.Meta.Common -import qualified Aws.Lambda.Runtime.Common as Runtime - -{-| Helper function that the dispatcher will use to -decode the JSON that comes as an AWS Lambda event into the -appropriate type expected by the handler. --} -decodeObj :: FromJSON a => String -> a -decodeObj x = - case (eitherDecode $ LazyByteString.pack x) of - Left e -> error e - Right v -> v - -{-| Helper function that the dispatcher will use to -decode the JSON that comes as an AWS Lambda event into the -appropriate type expected by the handler. --} -encodeObj :: ToJSON a => a -> String -encodeObj x = LazyByteString.unpack (encode x) - - -{-| Generates the dispatcher out of a list of -handler names in the form @src/Foo/Bar.handler@ - -This dispatcher has a case for each of the handlers that calls -the appropriate qualified function. In the case of the example above, -the dispatcher will call @Foo.Bar.handler@. --} -generate :: [Text] -> Meta.ExpQ -generate handlerNames = do - caseExp <- expressionName "functionHandler" - matches <- traverse handlerCase handlerNames - unmatched <- unmatchedCase - pure $ Meta.CaseE caseExp (matches <> [unmatched]) - -handlerCase :: Text -> Meta.MatchQ -handlerCase lambdaHandler = do - let pat = Meta.LitP (Meta.StringL $ Text.unpack lambdaHandler) - body <- [e|do - result <- $(expressionName qualifiedName) (decodeObj $(expressionName "eventObject")) (decodeObj $(expressionName "contextObject")) --- ($(constructorName "LambdaResult") . encodeObj) - either (pure . Left . encodeObj) (pure . Right . $(constructorName "LambdaResult") . encodeObj) result |] - pure $ Meta.Match pat (Meta.NormalB body) [] - where - qualifiedName = - lambdaHandler - & Text.dropWhile (/= '/') - & Text.drop 1 - & Text.replace "/" "." - -unmatchedCase :: Meta.MatchQ -unmatchedCase = do - let pattern = Meta.WildP - body <- [e| - pure $ Left ("Handler " <> $(expressionName "functionHandler") <> " does not exist on project") - |] - pure $ Meta.Match pattern (Meta.NormalB body) [] diff --git a/src/Aws/Lambda/Meta/Main.hs b/src/Aws/Lambda/Meta/Main.hs index 78d5746..e78201e 100644 --- a/src/Aws/Lambda/Meta/Main.hs +++ b/src/Aws/Lambda/Meta/Main.hs @@ -1,29 +1,21 @@ {-| main function generation for interoperation with the layer -} module Aws.Lambda.Meta.Main ( Runtime.LambdaOptions(..) - , generateIPC - , generateDirectCall - , Options.getRecord + , generate ) where import qualified Language.Haskell.TH as Meta -import qualified Options.Generic as Options import Aws.Lambda.Meta.Common import qualified Aws.Lambda.Runtime.Common as Runtime --- | Generate the main function that the layer will call -generateIPC :: Meta.DecsQ -generateIPC = [d| - $(declarationName "main") = getRecord "" >>= run - |] - -generateDirectCall :: Meta.DecsQ -generateDirectCall = [d| +-- | Generate the main function with the dispatcher +generate :: Meta.DecsQ +generate = [d| $(declarationName "main") = $(directCallBody) |] where directCallBody = [e|do - runLambda $ $(constructorName "DirectCall") run + runLambda run |] diff --git a/src/Aws/Lambda/Meta/Run.hs b/src/Aws/Lambda/Meta/Run.hs index 2772b08..733653f 100644 --- a/src/Aws/Lambda/Meta/Run.hs +++ b/src/Aws/Lambda/Meta/Run.hs @@ -1,6 +1,5 @@ module Aws.Lambda.Meta.Run ( generate - , generateNoIPC ) where import qualified Language.Haskell.TH as Meta @@ -8,24 +7,10 @@ import qualified Language.Haskell.TH as Meta import Aws.Lambda.Meta.Common import qualified Aws.Lambda.Meta.Discover as Discover import qualified Aws.Lambda.Meta.Dispatch as Dispatch -import qualified Aws.Lambda.Meta.DispatchNoIPC as DispatchNoIPC -{-| Generate the run function - -It will create a dispatcher that is a huge @case@ expression that -expects the name of the handler provided by AWS Lambda, and will -execute the appropriate user function - -} generate :: Meta.DecQ generate = do handlers <- Meta.runIO Discover.handlers clause' <- getFieldsFrom "LambdaOptions" ["functionHandler", "contextObject", "eventObject", "executionUuid"] body <- Dispatch.generate handlers pure $ Meta.FunD (Meta.mkName "run") [Meta.Clause [clause'] (Meta.NormalB body) []] - -generateNoIPC :: Meta.DecQ -generateNoIPC = do - handlers <- Meta.runIO Discover.handlers - clause' <- getFieldsFrom "LambdaOptions" ["functionHandler", "contextObject", "eventObject", "executionUuid"] - body <- DispatchNoIPC.generate handlers - pure $ Meta.FunD (Meta.mkName "run") [Meta.Clause [clause'] (Meta.NormalB body) []] diff --git a/src/Aws/Lambda/Runtime.hs b/src/Aws/Lambda/Runtime.hs index a72e98f..cf559d3 100644 --- a/src/Aws/Lambda/Runtime.hs +++ b/src/Aws/Lambda/Runtime.hs @@ -1,6 +1,5 @@ module Aws.Lambda.Runtime ( runLambda - , Runtime.Mode(..) , Runtime.LambdaResult(..) ) where @@ -15,20 +14,19 @@ import qualified Aws.Lambda.Runtime.ApiInfo as ApiInfo import qualified Aws.Lambda.Runtime.Context as Context import qualified Aws.Lambda.Runtime.Environment as Environment import qualified Aws.Lambda.Runtime.Error as Error -import qualified Aws.Lambda.Runtime.IPC as IPC import qualified Aws.Lambda.Runtime.Publish as Publish import qualified Aws.Lambda.Runtime.Common as Runtime -- | Runs the user @haskell_lambda@ executable and posts back the -- results. This is called from the layer's @main@ function. -runLambda :: Runtime.Mode -> IO () -runLambda mode = do +runLambda :: Runtime.RunCallback -> IO () +runLambda callback = do manager <- Http.newManager httpManagerSettings forever $ do lambdaApi <- Environment.apiEndpoint `catch` variableNotSet event <- ApiInfo.fetchEvent manager lambdaApi `catch` errorParsing context <- Context.initialize event `catch` errorParsing `catch` variableNotSet - ((invokeAndRun mode manager lambdaApi event context + ((invokeAndRun callback manager lambdaApi event context `catch` \err -> Publish.parsingError err lambdaApi context manager) `catch` \err -> Publish.invocationError err lambdaApi context manager) `catch` \(err :: Error.EnvironmentVariableNotSet) -> Publish.runtimeInitError err lambdaApi context manager @@ -41,45 +39,40 @@ httpManagerSettings = } invokeAndRun - :: Throws Error.Parsing - => Throws Error.Invocation + :: Throws Error.Invocation => Throws Error.EnvironmentVariableNotSet - => Runtime.Mode + => Runtime.RunCallback -> Http.Manager -> String -> ApiInfo.Event -> Context.Context -> IO () -invokeAndRun mode manager lambdaApi event context = do - result <- invokeWithMode mode event context +invokeAndRun callback manager lambdaApi event context = do + result <- invokeWithMode callback event context Publish.result result lambdaApi context manager `catch` \err -> Publish.invocationError err lambdaApi context manager invokeWithMode :: Throws Error.Invocation - => Throws Error.Parsing => Throws Error.EnvironmentVariableNotSet - => Runtime.Mode + => Runtime.RunCallback -> ApiInfo.Event -> Context.Context -> IO Runtime.LambdaResult -invokeWithMode mode event context = - case mode of - Runtime.IPC -> IPC.invoke (ApiInfo.event event) context - (Runtime.DirectCall f) -> do - handlerName <- Environment.handlerName - let lambdaOptions = Runtime.LambdaOptions - { eventObject = LazyByteString.unpack $ ApiInfo.event event - , contextObject = LazyByteString.unpack . encode $ context - , functionHandler = handlerName - , executionUuid = "" -- DirectCall doesnt use UUID - } - result <- f lambdaOptions - case result of - Left err -> - throw $ Error.Invocation err - Right value -> - pure value +invokeWithMode callback event context = do + handlerName <- Environment.handlerName + let lambdaOptions = Runtime.LambdaOptions + { eventObject = LazyByteString.unpack $ ApiInfo.event event + , contextObject = LazyByteString.unpack . encode $ context + , functionHandler = handlerName + , executionUuid = "" -- DirectCall doesnt use UUID + } + result <- callback lambdaOptions + case result of + Left err -> + throw $ Error.Invocation err + Right value -> + pure value variableNotSet :: Error.EnvironmentVariableNotSet -> IO a variableNotSet (Error.EnvironmentVariableNotSet env) = diff --git a/src/Aws/Lambda/Runtime/Common.hs b/src/Aws/Lambda/Runtime/Common.hs index f038111..c486647 100644 --- a/src/Aws/Lambda/Runtime/Common.hs +++ b/src/Aws/Lambda/Runtime/Common.hs @@ -1,26 +1,14 @@ module Aws.Lambda.Runtime.Common - ( Mode(..) + ( RunCallback , LambdaResult(..) , LambdaOptions(..) ) where import GHC.Generics (Generic) -import qualified Options.Generic as Options -{-| Mode of calling the user functions. - -It can be 'IPC' (inter-process communication), where the -dispatcher will spawn a process with the handlers of the -user. (Used when using the layer) - -Or, it can be 'DirectCall', for when the handlers are in -the same process. (The runtime is bootstrapped with the -project). --} -data Mode - = IPC - | DirectCall (LambdaOptions -> IO (Either String LambdaResult)) - -- ^ This horrible signature implies the following +-- | Callback that we pass to the dispatcher function +type RunCallback = + LambdaOptions -> IO (Either String LambdaResult) -- | Options that the generated main expects data LambdaOptions = LambdaOptions @@ -28,7 +16,7 @@ data LambdaOptions = LambdaOptions , contextObject :: !String , functionHandler :: !String , executionUuid :: !String - } deriving (Generic, Options.ParseRecord) + } deriving (Generic) -- | Wrapper type to handle the result of the user newtype LambdaResult = diff --git a/src/Aws/Lambda/Runtime/IPC.hs b/src/Aws/Lambda/Runtime/IPC.hs deleted file mode 100644 index c58cce3..0000000 --- a/src/Aws/Lambda/Runtime/IPC.hs +++ /dev/null @@ -1,116 +0,0 @@ -{-| Inter-Process Communication - -Used for when the user project is called from a layer. - -This is used to call the @haskell_lambda@ executable, which is -provided by the user, when they want to use the layer. - -This IPC protocol is based on printing an UUID that is -created by the layer, and then the result. So everything that -is printed before the UUID, is considered STDOUT printed by -the lambda, while what comes after the UUID is considered. - -In the case that the lambda execution fails, the exit code -won't be 0 (exit-success), so it will use the STDERR. --} -module Aws.Lambda.Runtime.IPC - ( invoke - , returnAndFail - , returnAndSucceed - ) where - - -import Data.Function ((&)) -import qualified Data.Maybe as Maybe -import qualified Data.String as String -import qualified System.Exit as Exit -import qualified System.IO as IO -import qualified System.Process as Process - -import Control.Exception.Safe.Checked -import Data.Aeson -import qualified Data.ByteString.Lazy.Char8 as ByteString -import qualified Data.UUID as UUID -import qualified Data.UUID.V4 as UUID - -import Aws.Lambda.Runtime.Context (Context (..)) -import qualified Aws.Lambda.Runtime.Environment as Environment -import qualified Aws.Lambda.Runtime.Error as Error -import Aws.Lambda.Runtime.Common - --- | Returns the JSON value failing, according to the protocol -returnAndFail :: ToJSON a => String -> a -> IO () -returnAndFail uuid v = do - IO.hFlush IO.stdout - putStrLn uuid - IO.hFlush IO.stdout - putStrLn (ByteString.unpack $ encode v) - IO.hFlush IO.stdout - IO.hFlush IO.stderr - Exit.exitFailure - --- | Returns the JSON value succeeding, according to the protocol -returnAndSucceed :: ToJSON a => String -> a -> IO () -returnAndSucceed uuid v = do - IO.hFlush IO.stdout - putStrLn uuid - IO.hFlush IO.stdout - putStrLn (ByteString.unpack $ encode v) - IO.hFlush IO.stdout - Exit.exitSuccess - --- | Invokes a function defined by the user as the @haskell_lambda@ executable -invoke - :: Throws Error.Invocation - => Throws Error.Parsing - => Throws Error.EnvironmentVariableNotSet - => ByteString.ByteString - -> Context - -> IO LambdaResult -invoke event context = do - handlerName <- Environment.handlerName - runningDirectory <- Environment.taskRoot - let contextJSON = ByteString.unpack $ encode context - uuid <- UUID.nextRandom - out <- Process.readProcessWithExitCode (runningDirectory <> "/haskell_lambda") - [ "--eventObject", ByteString.unpack event - , "--contextObject", contextJSON - , "--functionHandler", handlerName - , "--executionUuid", UUID.toString uuid - ] - "" - case out of - (Exit.ExitSuccess, stdOut, _) -> do - res <- getFunctionResult uuid stdOut - case res of - Nothing -> throw (Error.Parsing "parsing result" stdOut) - Just value -> pure (LambdaResult value) - (_, stdOut, stdErr) -> - if stdErr /= "" - then throw (Error.Invocation stdErr) - else do - res <- getFunctionResult uuid stdOut - case res of - Nothing -> throw (Error.Parsing "parsing error" stdOut) - Just value -> throw (Error.Invocation value) - -getFunctionResult :: UUID.UUID -> String -> IO (Maybe String) -getFunctionResult u stdOut = do - let out = String.lines stdOut - let uuid = UUID.toString u - printAfterUuid uuid out - returnAfterUuid uuid out - where - printAfterUuid uuid out = - out - & takeWhile (/= uuid) - & mapM_ ( \t -> do - putStrLn t - IO.hFlush IO.stdout ) - - returnAfterUuid uuid out = - out - & dropWhile (/= uuid) - & dropWhile (== uuid) - & Maybe.listToMaybe - & pure From a96a4a11bd7d9dcae75e816915d3a7582547f2ec Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Wed, 19 Jun 2019 17:30:28 +0100 Subject: [PATCH 3/7] Rename to proper thing --- src/Aws/Lambda/Runtime.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Aws/Lambda/Runtime.hs b/src/Aws/Lambda/Runtime.hs index cf559d3..263f08c 100644 --- a/src/Aws/Lambda/Runtime.hs +++ b/src/Aws/Lambda/Runtime.hs @@ -48,18 +48,18 @@ invokeAndRun -> Context.Context -> IO () invokeAndRun callback manager lambdaApi event context = do - result <- invokeWithMode callback event context + result <- invokeWithCallback callback event context Publish.result result lambdaApi context manager `catch` \err -> Publish.invocationError err lambdaApi context manager -invokeWithMode +invokeWithCallback :: Throws Error.Invocation => Throws Error.EnvironmentVariableNotSet => Runtime.RunCallback -> ApiInfo.Event -> Context.Context -> IO Runtime.LambdaResult -invokeWithMode callback event context = do +invokeWithCallback callback event context = do handlerName <- Environment.handlerName let lambdaOptions = Runtime.LambdaOptions { eventObject = LazyByteString.unpack $ ApiInfo.event event From 4283776ebcdf5bbc79f3edbb7ed33473e35a32fc Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Thu, 20 Jun 2019 11:52:03 +0100 Subject: [PATCH 4/7] Stop deploying the layer --- .circleci/config.yml | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1b38118..5e0bcd1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -63,42 +63,12 @@ jobs: - store_test_results: path: ~/.stack-work/logs/* - deploy: - docker: - - image: circleci/python:2.7-jessie - working_directory: ~/aws-lambda-haskell-runtime - steps: - - checkout - - restore-cache: - key: zip-file - paths: - - "/tmp/" - - run: - name: Install awscli - command: sudo pip install awscli - - run: - name: Deploy to AWS Layers - command: | - LAYER_NAME="aws-haskell-runtime" - REGIONS_LIST="us-east-1 us-east-2 us-west-1 us-west-2 ap-south-1 ap-northeast-2 ap-southeast-1 ap-southeast-2 ap-northeast-1 ca-central-1 eu-central-1 eu-west-1 eu-west-2 eu-west-3 eu-north-1 sa-east-1" - for region in $REGIONS_LIST - do - aws lambda publish-layer-version --layer-name $LAYER_NAME --zip-file fileb:///tmp/runtime.zip --region $region - aws lambda add-layer-version-permission --layer-name $LAYER_NAME --version-number $(aws lambda list-layer-versions --layer-name $LAYER_NAME --region $region | grep Version | tail -n+3 | head -n 1 | cut -d: -f2- | rev | cut -d, -f2 | rev) --principal '*' --action lambda:GetLayerVersion --statement-id allow-getLayerVersion-all --region $region - done - workflows: version: 2 build-and-deploy: jobs: - build - test - - deploy: - requires: - - build - filters: - branches: - only: master general: branches: From 3570207874b745d20ec506df4d0fea9634864fb7 Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Thu, 20 Jun 2019 11:53:22 +0100 Subject: [PATCH 5/7] Rename --- src/Aws/Lambda/Configuration.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Aws/Lambda/Configuration.hs b/src/Aws/Lambda/Configuration.hs index 8ec3326..51088ca 100644 --- a/src/Aws/Lambda/Configuration.hs +++ b/src/Aws/Lambda/Configuration.hs @@ -1,7 +1,7 @@ {-# OPTIONS_GHC -fno-warn-unused-pattern-binds #-} module Aws.Lambda.Configuration ( Main.LambdaOptions(..) - , configureLambda + , generateLambdaDispatcher , Dispatch.decodeObj , Dispatch.encodeObj ) @@ -15,8 +15,8 @@ import qualified Aws.Lambda.Meta.Run as Run {-| Generates a @main@ function that acts as a dispatcher -} -configureLambda :: Meta.DecsQ -configureLambda = do +generateLambdaDispatcher :: Meta.DecsQ +generateLambdaDispatcher = do main <- Main.generate run <- Run.generate return (main <> [run]) From f7ca316feb3023487bddae6cbeea96ef2ce90b5a Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Thu, 20 Jun 2019 11:58:12 +0100 Subject: [PATCH 6/7] Stylistic changes --- ChangeLog.md | 3 --- LICENSE | 2 +- Makefile | 11 ----------- README.md | 13 ++----------- src/Aws/Lambda/Runtime.hs | 2 +- src/Aws/Lambda/Runtime/Common.hs | 2 +- src/Aws/Lambda/Runtime/Publish.hs | 2 +- stack-template.hsfiles | 14 +++++++------- 8 files changed, 13 insertions(+), 36 deletions(-) delete mode 100644 ChangeLog.md delete mode 100644 Makefile diff --git a/ChangeLog.md b/ChangeLog.md deleted file mode 100644 index 776e058..0000000 --- a/ChangeLog.md +++ /dev/null @@ -1,3 +0,0 @@ -# Changelog for aws-lambda-haskell-runtime - -## Unreleased changes diff --git a/LICENSE b/LICENSE index 3128d20..2e96ff8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2018 The Agile Monkeys +Copyright 2019 The Agile Monkeys Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/Makefile b/Makefile deleted file mode 100644 index f0d21d5..0000000 --- a/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -deploy: - @stack build --docker - @cp `stack --docker path --local-install-root`/bin/bootstrap . - @zip runtime.zip bootstrap - aws lambda publish-layer-version --layer-name haskell-runtime-dev --zip-file fileb://runtime.zip - @rm bootstrap runtime.zip - -# USAGE: make VERSION=42 publish -# Replace 42 with the version you are publishing -publish: - aws lambda add-layer-version-permission --layer-name haskell-runtime-dev --version-number ${VERSION} --principal "*" --statement-id publish --action lambda:GetLayerVersion \ No newline at end of file diff --git a/README.md b/README.md index f6ef5fc..a042e2d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This package provides a way of running Haskell projects on AWS Lambda. ## Sample lambda function ``` -stack new my-haskell-lambda https://github.com/theam/aws-lambda-haskell-runtime/raw/master/stack-template.hsfiles --resolver=lts-13.0 --omit-packages +stack new my-haskell-lambda https://github.com/theam/aws-lambda-haskell-runtime/raw/master/stack-template.hsfiles --resolver=lts-13.25 --omit-packages cd my-haskell-lambda stack docker pull ``` @@ -21,7 +21,7 @@ packages: - . extra-deps: -- aws-lambda-haskell-runtime-1.0.10 +- aws-lambda-haskell-runtime-2.0.0 ``` to your `stack.yaml` @@ -34,15 +34,6 @@ make Now you should have a `build/function.zip` file that you can upload to your lambda. -## Lambda function configuration - -When creating your lambda function you need to provide a layer with the Haskell runtime. We have deployed the layer to our AWS account, you can use it or deploy it to your own AWS account. - -The ARN of the runtime layer is: -``` -arn:aws:lambda::785355572843:layer:aws-haskell-runtime:5 -```` - ## Full user guide Take a look at the [Getting Started with the Haskell AWS Lambda Runtime](https://medium.com/the-theam-journey/getting-started-with-the-haskell-aws-lambda-runtime-951b2322c7a3) guide. diff --git a/src/Aws/Lambda/Runtime.hs b/src/Aws/Lambda/Runtime.hs index 263f08c..02f76df 100644 --- a/src/Aws/Lambda/Runtime.hs +++ b/src/Aws/Lambda/Runtime.hs @@ -11,11 +11,11 @@ import Data.Aeson import qualified Data.ByteString.Lazy.Char8 as LazyByteString import qualified Aws.Lambda.Runtime.ApiInfo as ApiInfo +import qualified Aws.Lambda.Runtime.Common as Runtime import qualified Aws.Lambda.Runtime.Context as Context import qualified Aws.Lambda.Runtime.Environment as Environment import qualified Aws.Lambda.Runtime.Error as Error import qualified Aws.Lambda.Runtime.Publish as Publish -import qualified Aws.Lambda.Runtime.Common as Runtime -- | Runs the user @haskell_lambda@ executable and posts back the -- results. This is called from the layer's @main@ function. diff --git a/src/Aws/Lambda/Runtime/Common.hs b/src/Aws/Lambda/Runtime/Common.hs index c486647..88d00ed 100644 --- a/src/Aws/Lambda/Runtime/Common.hs +++ b/src/Aws/Lambda/Runtime/Common.hs @@ -20,4 +20,4 @@ data LambdaOptions = LambdaOptions -- | Wrapper type to handle the result of the user newtype LambdaResult = - LambdaResult String \ No newline at end of file + LambdaResult String diff --git a/src/Aws/Lambda/Runtime/Publish.hs b/src/Aws/Lambda/Runtime/Publish.hs index bc302cf..685a9f6 100644 --- a/src/Aws/Lambda/Runtime/Publish.hs +++ b/src/Aws/Lambda/Runtime/Publish.hs @@ -13,9 +13,9 @@ import qualified Data.ByteString.Char8 as ByteString import qualified Network.HTTP.Client as Http import qualified Aws.Lambda.Runtime.API.Endpoints as Endpoints +import Aws.Lambda.Runtime.Common import Aws.Lambda.Runtime.Context (Context (..)) import qualified Aws.Lambda.Runtime.Error as Error -import Aws.Lambda.Runtime.Common -- | Publishes the result back to AWS Lambda result :: LambdaResult -> String -> Context -> Http.Manager -> IO () diff --git a/stack-template.hsfiles b/stack-template.hsfiles index e42a083..430fc20 100644 --- a/stack-template.hsfiles +++ b/stack-template.hsfiles @@ -11,14 +11,14 @@ description: Please see the README on GitHub at = 4.7 && < 5 -- aws-lambda-haskell-runtime >= 1.0.9 +- aws-lambda-haskell-runtime >= 2.0.0 - aeson library: source-dirs: src executables: - haskell_lambda: + bootstrap: main: Main.hs source-dirs: app ghc-options: @@ -43,15 +43,16 @@ all: @rm -rf ./build/* @stack clean --docker @stack build --docker - @cp `stack --docker path --local-install-root`/bin/haskell_lambda build - @cd build && zip function.zip haskell_lambda && rm haskell_lambda && cd .. + @cp `stack --docker path --local-install-root`/bin/bootstrap build + @cd build && zip function.zip bootstrap && rm bootstrap && cd .. {-# START_FILE src/Lib.hs #-} module Lib where import GHC.Generics -import Aws.Lambda.Runtime import Data.Aeson +import Aws.Lambda + data Person = Person { personName :: String , personAge :: Int @@ -69,8 +70,7 @@ handler person context = {-# START_FILE app/Main.hs #-} module Main where -import Aws.Lambda.Configuration -import Aws.Lambda.Runtime +import Aws.Lambda import qualified Lib From a01c8b4ce42c01fdea5c12b4f4dd2c63b144cad0 Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Thu, 20 Jun 2019 14:35:28 +0100 Subject: [PATCH 7/7] Fix build --- .circleci/config.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5e0bcd1..b3eb389 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -20,11 +20,6 @@ jobs: - stack-{{checksum "stack.yaml"}}-{{checksum "package.yaml"}} - run: stack setup - run: stack build - - run: cd $(stack path --local-install-root)/bin/ && zip -j /tmp/runtime.zip bootstrap - - save-cache: - key: zip-file - paths: - - "/tmp/runtime.zip" - save-cache: key: stack-{{ checksum "stack.yaml" }} paths: