packages feed

aws-lambda-haskell-runtime 1.1.0 → 2.0.1

raw patch · 17 files changed

+140/−273 lines, 17 filesdep −aws-lambda-haskell-runtimedep −optparse-genericdep −process

Dependencies removed: aws-lambda-haskell-runtime, optparse-generic, process, uuid

Files

− ChangeLog.md
@@ -1,3 +0,0 @@-# Changelog for aws-lambda-haskell-runtime--## Unreleased changes
LICENSE view
@@ -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.
README.md view
@@ -1,48 +1,7 @@-# Haskell Runtime for AWS Lambda-[![Hackage](https://img.shields.io/hackage/v/aws-lambda-haskell-runtime.svg)](https://hackage.haskell.org/package/aws-lambda-haskell-runtime)+[![CircleCI](https://circleci.com/gh/theam/aws-lambda-haskell-runtime.svg?style=shield)](https://circleci.com/gh/theam/aws-lambda-haskell-runtime)+[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=shield)](http://makeapullrequest.com)+[![Hackage version](https://img.shields.io/hackage/v/aws-lambda-haskell-runtime.svg)](https://hackage.haskell.org/package/aws-lambda-haskell-runtime)+[![Open Source Love png1](https://badges.frapsoft.com/os/v1/open-source.png?v=103)](https://github.com/ellerbrock/open-source-badges/) [![Linter](https://img.shields.io/badge/code%20style-HLint-brightgreen.svg)](https://github.com/ndmitchell/hlint)-[![CircleCI](https://circleci.com/gh/theam/aws-lambda-haskell-runtime/tree/master.svg?style=svg)](https://circleci.com/gh/theam/aws-lambda-haskell-runtime/tree/master) --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-cd my-haskell-lambda-stack docker pull-```--Then add--```-packages:-- .--extra-deps:-- aws-lambda-haskell-runtime-1.0.10-```--to your `stack.yaml`--Then run--```-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:<YOUR REGION>: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.+[Proceed to site](https://theam.github.io/aws-lambda-haskell-runtime) to know how to use the AWS Lambda Haskell Runtime.
− app/Main.hs
@@ -1,21 +0,0 @@--- | Main entry point for the layer-module Main-  ( 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)
aws-lambda-haskell-runtime.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: a4a6b9dc1371218a01d228e551520a6e1296955192cb06ca15f14ebe0923badd+-- hash: 938688bc6c929451cf9326354de6c4b1e8575302e64a2bd413d0b921cd6b3549  name:           aws-lambda-haskell-runtime-version:        1.1.0+version:        2.0.1 synopsis:       Haskell runtime for AWS Lambda description:    Please see the README on GitHub at <https://github.com/theam/aws-lambda-haskell-runtime#readme> category:       AWS@@ -21,7 +21,6 @@ build-type:     Simple extra-source-files:     README.md-    ChangeLog.md  source-repository head   type: git@@ -41,12 +40,11 @@       Aws.Lambda.Runtime.API.Endpoints       Aws.Lambda.Runtime.API.Version       Aws.Lambda.Runtime.ApiInfo+      Aws.Lambda.Runtime.Common       Aws.Lambda.Runtime.Context       Aws.Lambda.Runtime.Environment       Aws.Lambda.Runtime.Error-      Aws.Lambda.Runtime.IPC       Aws.Lambda.Runtime.Publish-      Aws.Lambda.Runtime.Result       Paths_aws_lambda_haskell_runtime   hs-source-dirs:       src@@ -58,28 +56,11 @@     , bytestring     , http-client     , http-types-    , optparse-generic     , path     , path-io-    , process     , safe-exceptions-checked     , template-haskell     , text-    , uuid-  default-language: Haskell2010--executable bootstrap-  main-is: Main.hs-  other-modules:-      Paths_aws_lambda_haskell_runtime-  hs-source-dirs:-      app-  default-extensions: TemplateHaskell OverloadedStrings RecordWildCards ScopedTypeVariables DeriveGeneric TypeApplications FlexibleContexts DeriveAnyClass QuasiQuotes-  ghc-options: -Wall -fno-warn-orphans -optP-Wno-nonportable-include-path -Wincomplete-uni-patterns -Wincomplete-record-updates -Wcompat -Widentities -Wredundant-constraints -Wmissing-export-lists -Wpartial-fields -fhide-source-paths -freverse-errors-  build-depends:-      aws-lambda-haskell-runtime-    , base >=4.7 && <5-    , http-client   default-language: Haskell2010  test-suite aws-lambda-haskell-runtime-test
src/Aws/Lambda.hs view
@@ -3,4 +3,5 @@   ) where  import Aws.Lambda.Configuration as Reexported+import Aws.Lambda.Runtime as Reexported import Aws.Lambda.Runtime.Context as Reexported
src/Aws/Lambda/Configuration.hs view
@@ -1,12 +1,9 @@ {-# OPTIONS_GHC -fno-warn-unused-pattern-binds #-} module Aws.Lambda.Configuration   ( Main.LambdaOptions(..)-  , Main.generate-  , Main.getRecord-  , configureLambda-  , IPC.returnAndFail-  , IPC.returnAndSucceed+  , generateLambdaDispatcher   , Dispatch.decodeObj+  , Dispatch.encodeObj   ) where @@ -15,13 +12,11 @@ import qualified Aws.Lambda.Meta.Dispatch as Dispatch 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+generateLambdaDispatcher :: Meta.DecsQ+generateLambdaDispatcher = do   main <- Main.generate   run <- Run.generate   return (main <> [run])
src/Aws/Lambda/Meta/Common.hs view
@@ -3,6 +3,7 @@   ( declarationName   , expressionName   , getFieldsFrom+  , constructorName   ) where  import Data.Text (Text)@@ -19,6 +20,10 @@ 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,
src/Aws/Lambda/Meta/Discover.hs view
@@ -1,6 +1,6 @@ {-| Discovery of AWS Lambda handlers A handler is basically a function that has a type definition that-starts with "handler :: ".+starts with "handler " and two colons.  -} module Aws.Lambda.Meta.Discover   ( handlers@@ -15,6 +15,14 @@ import Path import qualified Path.IO as PathIO +-- | Paths to ignore during compilation+ignoredPaths :: [Text]+ignoredPaths =+  [ "node_modules"+  , ".stack-work"+  , ".serverless"+  ]+ {-| Returns a list of handler paths that look like  @src/Foo/Bar/Quux.handler@@@ -35,7 +43,12 @@  where   isHaskellModule file =     fileExtension file == ".hs"+    && isNotIgnoredPath file +  isNotIgnoredPath file =+    filter (\ignoredPath -> ignoredPath `Text.isInfixOf` (Text.pack $ toFilePath file)) ignoredPaths+    & null+ handlerNames :: [Path Rel File] -> [Text] handlerNames modules =   fmap changeExtensionToHandler modules@@ -48,4 +61,7 @@ containsHandler :: Path Rel File -> IO Bool containsHandler file = do   fileContents <- readFile $ toFilePath file-  pure $ "handler :: " `Text.isInfixOf` Text.pack fileContents+  lines fileContents+    & filter (Text.isPrefixOf "handler :: " . Text.pack)+    & (not . null)+    & pure
src/Aws/Lambda/Meta/Dispatch.hs view
@@ -2,17 +2,21 @@ module Aws.Lambda.Meta.Dispatch   ( generate   , decodeObj+  , encodeObj+  , Runtime.LambdaResult(..)   ) where  import Data.Function ((&)) import Data.Text (Text) import qualified Data.Text as Text+import qualified Data.Char as Char  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@@ -24,6 +28,14 @@     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,19 +55,19 @@   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 =     lambdaHandler-    & Text.dropWhile (/= '/')-    & Text.drop 1-    & Text.replace "/" "."+    & Text.splitOn "/"+    & filter (Char.isUpper . Text.head)+    & Text.intercalate "."  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) []
src/Aws/Lambda/Meta/Main.hs view
@@ -1,27 +1,21 @@ {-| main function generation for interoperation with the layer -} module Aws.Lambda.Meta.Main-  ( LambdaOptions(..)+  ( Runtime.LambdaOptions(..)   , generate-  , 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 the main function with the dispatcher generate :: Meta.DecsQ generate = [d|-  $(declarationName "main") = getRecord "" >>= run+  $(declarationName "main") = $(directCallBody)   |]+ where+  directCallBody =+    [e|do+    runLambda run+    |]
src/Aws/Lambda/Meta/Run.hs view
@@ -8,12 +8,6 @@ import qualified Aws.Lambda.Meta.Discover as Discover import qualified Aws.Lambda.Meta.Dispatch as Dispatch -{-| 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
src/Aws/Lambda/Runtime.hs view
@@ -1,44 +1,78 @@ module Aws.Lambda.Runtime   ( runLambda+  , 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.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.IPC as IPC import qualified Aws.Lambda.Runtime.Publish as Publish  -- | 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.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 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 +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.Invocation   => Throws Error.EnvironmentVariableNotSet-  => Http.Manager+  => Runtime.RunCallback+  -> Http.Manager   -> String   -> ApiInfo.Event   -> Context.Context   -> IO ()-invokeAndRun manager lambdaApi event context = do-  result    <- IPC.invoke (ApiInfo.event event) context+invokeAndRun callback manager lambdaApi event context = do+  result    <- invokeWithCallback callback event context   Publish.result result lambdaApi context manager     `catch` \err -> Publish.invocationError err lambdaApi context manager++invokeWithCallback+  :: Throws Error.Invocation+  => Throws Error.EnvironmentVariableNotSet+  => Runtime.RunCallback+  -> ApiInfo.Event+  -> Context.Context+  -> IO Runtime.LambdaResult+invokeWithCallback 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) =
+ src/Aws/Lambda/Runtime/Common.hs view
@@ -0,0 +1,23 @@+module Aws.Lambda.Runtime.Common+  ( RunCallback+  , LambdaResult(..)+  , LambdaOptions(..)+  ) where++import GHC.Generics (Generic)++-- | Callback that we pass to the dispatcher function+type RunCallback =+  LambdaOptions -> IO (Either String LambdaResult)++-- | Options that the generated main expects+data LambdaOptions = LambdaOptions+  { eventObject     :: !String+  , contextObject   :: !String+  , functionHandler :: !String+  , executionUuid   :: !String+  } deriving (Generic)++-- | Wrapper type to handle the result of the user+newtype LambdaResult =+  LambdaResult String
− src/Aws/Lambda/Runtime/IPC.hs
@@ -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.Result (LambdaResult (..))---- | 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
src/Aws/Lambda/Runtime/Publish.hs view
@@ -13,9 +13,9 @@ 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.Result (LambdaResult (..))  -- | Publishes the result back to AWS Lambda result :: LambdaResult -> String -> Context -> Http.Manager -> IO ()
− src/Aws/Lambda/Runtime/Result.hs
@@ -1,7 +0,0 @@-module Aws.Lambda.Runtime.Result-  ( LambdaResult(..)-  ) where---- | Wrapper type to handle the result of the user-newtype LambdaResult =-  LambdaResult String