├── .gitignore ├── .gitmodules ├── AWS ├── ApiGateway.hs └── Lambda.hs ├── LICENSE ├── README.md ├── System └── Config.hs ├── cloudwatch.png ├── example-src ├── LICENSE ├── main.cabal ├── main.hs └── stack.yaml ├── ghc-centos └── Dockerfile ├── main.cabal ├── main.hs ├── run-tmpl.js ├── stack.yaml └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.o 3 | *.hi 4 | .stack-work/ 5 | \#* 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "system-extra"] 2 | path = system-extra 3 | url = git@github.com:abailly/system-extra.git 4 | [submodule "hs-word2vec"] 5 | path = hs-word2vec 6 | url = git@github.com:abailly/hs-word2vec.git 7 | -------------------------------------------------------------------------------- /AWS/ApiGateway.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ScopedTypeVariables #-} 2 | module AWS.ApiGateway where 3 | 4 | import Control.Lens 5 | import Control.Monad (when) 6 | import Control.Monad.Catch 7 | import Control.Monad.Trans.AWS 8 | import Control.Monad.Trans.Resource 9 | import Data.Functor (void) 10 | import Data.Maybe 11 | import Data.Text (Text) 12 | import Network.AWS.APIGateway 13 | 14 | createApi :: (MonadResource m, MonadCatch m) => Text -> Text -> AWST m Method 15 | createApi api _ = do 16 | restApi <- send (createRestAPI api) 17 | let Just apiId = restApi ^. raId 18 | resources :: [Resource] <- view grrsItems <$> send (getResources apiId) 19 | let Just rootId = head resources ^. rId 20 | resrc :: Resource <- send (createResource apiId rootId "{file}") 21 | let Just fileResourceId = resrc ^. rId 22 | meth :: Method <- send (putMethod apiId fileResourceId "GET" "NONE") 23 | return meth 24 | 25 | deleteApi :: (MonadResource m, MonadCatch m) => Text -> AWST m () 26 | deleteApi apiName = do 27 | restApis <- (listToMaybe . view grarsItems) <$> send getRestAPIs 28 | mapM_ deleteApiWithName restApis 29 | where 30 | deleteApiWithName api = when (api ^. raName == Just apiName) $ do 31 | let apiId = api ^. raId 32 | maybe (return ()) (void . send . deleteRestAPI) apiId 33 | -------------------------------------------------------------------------------- /AWS/Lambda.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | module AWS.Lambda where 3 | 4 | import Control.Lens 5 | import Control.Monad.Catch 6 | import Control.Monad.Trans (liftIO) 7 | import Control.Monad.Trans.AWS 8 | import Control.Monad.Trans.Resource 9 | import qualified Data.ByteString.Lazy as LBS 10 | import Data.Text (Text) 11 | import Network.AWS.Lambda 12 | 13 | -- | Default role to use for creating function 14 | -- 15 | -- TODO: parameterize 16 | defaultRole :: Text 17 | defaultRole = "arn:aws:iam::259394719635:role/lambda" 18 | 19 | defaultHandler :: Text 20 | defaultHandler = "run.handle" 21 | 22 | createFunctionWithZip :: (MonadResource m, MonadCatch m) => Text -> FilePath -> AWST m FunctionConfiguration 23 | createFunctionWithZip fName zipFile = do 24 | code <- liftIO $ LBS.readFile zipFile 25 | send (createFunction fName NODEJS4_3 defaultRole defaultHandler (set fcZipFile (Just $ LBS.toStrict code) functionCode)) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abailly/aws-lambda-haskell/df9afb7498500c2282515752f905a6e05bba9658/LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Running Haskell AWS Lambda Functions 2 | 3 | This repository contains code and directions to run Haskell executables over [AWS Lambda]() "serverless" infrastructure. This experiment was triggered by reading description of [apex](http://apex.run) provide a wrapper to run Go code. 4 | 5 | ## Howto - Automated Way 6 | 7 | The `main.hs` aims at automating the deployment of Haskell code packages to AWS Lambda: 8 | 9 | * Manaing AWS Lambda functions and packages, 10 | * Manage AWS Api Gateway endpoints to expose Lambda functions. 11 | 12 | Interaction with AWS is done through the excellent [amazonka](https://github.com/brendanhay/amazonka) package. 13 | 14 | To build CLI program: 15 | 16 | ``` 17 | $ stack build 18 | ``` 19 | 20 | Creating an AWS Lambda package (requires [docker](http://docker.io)): 21 | 22 | ``` 23 | $ ./main lambda build --build-target foo --source-directory foo/ 24 | ``` 25 | 26 | This generates a `lambda.zip` file in the current directory that contains something like: 27 | 28 | ``` 29 | $ unzip -l lambda.zip 30 | Archive: lambda.zip 31 | Length Date Time Name 32 | -------- ---- ---- ---- 33 | 1131 06-07-16 10:38 run.js 34 | 32648176 06-07-16 10:38 words 35 | 364776 06-07-16 10:38 libblas.so.3 36 | 5638304 06-07-16 10:38 liblapack.so.3 37 | 1186104 06-07-16 10:38 libgfortran.so.3 38 | 244904 06-07-16 10:38 libquadmath.so.0 39 | -------- ------- 40 | 40083395 6 files 41 | ``` 42 | 43 | Deploying an existing `lambda.zip` file to AWS: 44 | 45 | ``` 46 | $ ./main lambda deploy --function-name foo 47 | ``` 48 | 49 | This creates function `foo` that can be invoked manually (see below). 50 | 51 | Deleting an API Gateway endpoint: 52 | 53 | ``` 54 | $ ./main api delete --endpoint fooAPI 55 | ``` 56 | 57 | Note that AWS has a rate limit on API deletions. 58 | 59 | ### TODO 60 | 61 | * Creating an API Gateway endpoint and linking it to Lambda function: The configuration and part of the code are ready but the process is a little bit involved hence requires more interaction with AWS 62 | 63 | ## Howto - The Manual Way 64 | 65 | ### Prepare environment 66 | 67 | * Ensure access to AWS Lambda service: 68 | * use `aws configure` to define credentials and region to deploy to 69 | * create or use role for executing code on AWS Lambda, e.g. something like `arn:aws:iam::259394719635:role/lambda` 70 | 71 | ## Simple Build ## 72 | 73 | * Build docker container for building Haskell code that is supposed to be runnable on Amazon's Linux AMI 74 | 75 | ``` 76 | cd ghc-centos 77 | docker build -t haskell-centos . 78 | cd .. 79 | ``` 80 | 81 | * Build haskell code: 82 | 83 | ``` 84 | docker run -ti -v $(pwd):/build -w /build --name=haskell-build haskell-centos stack build --allow-different-user 85 | ... 86 | CONTAINER_ID=$(docker ps -a | grep haskell-centos | head -1 | cut -d ' ' -f 1) 87 | docker run --volumes-from=$CONTAINER_ID busybox dd if=/build/.stack-work/install/x86_64-linux/ghc-7.10.3/7.10.3/bin/main > main 88 | ``` 89 | 90 | * Build zip file containing Javascript wrapper and main app 91 | 92 | ``` 93 | zip lambda.zip run.js main 94 | ``` 95 | 96 | ## Complex Build 97 | 98 | A lot of interesting pieces of code rely on external C libraries. For example, [hmatrix](https://github.com/albertoruiz/hmatrix) relies on LAPACK and BLAS libraries for efficient matrix operations and compiled executable will need to be dynamically (or statically) linked to those libraries for proper execution. The above directions should be updated to take into account those extraneous libraries: 99 | 100 | * Add the needed libraries into the build container description, e.g. 101 | 102 | ``` 103 | RUN yum install -y lapack-devel blas-devel 104 | ``` 105 | 106 | * Build code as before, using the custom image, 107 | * Export from container the needed libraries (the will be packed as part of the code shipped to AWS Lambda): 108 | 109 | ``` 110 | $ docker run --volumes-from=$CONTAINER_ID haskell-centos ldd /build/.stack-work/install/x86_64-linux/ghc-7.10.3/7.10.3/bin/main > libs 111 | ... extract list of library files to copy... 112 | $ for i in $(cat libraryFiles); do 113 | docker run --volumes-from=$CONTAINER_ID haskell-centos dd if=$i > $(basename $i) 114 | done 115 | ``` 116 | * Modify the `run.js` wrapper to set correctly `LD_LIBRARY_PATH` environment: 117 | 118 | ``` 119 | process.env['LD_LIBRARY_PATH'] = process.env['LAMBDA_TASK_ROOT'] 120 | ctx = context 121 | ``` 122 | * Pack everything into `lambda.zip`: Javascript wrapper, libraries, executable... 123 | 124 | ## Deploy to AWS Lambda 125 | 126 | * Create function on Lambda: 127 | 128 | ``` 129 | $ aws lambda create-function --function-name hello-test --runtime nodejs4.3 --zip-file fileb://./aws-lambda.zip --handler run.handle --role arn:aws:iam::259394719635:role/lambda 130 | { 131 | "CodeSha256": "QYKOebaDN/fqEzb1nmaV3ByNDZK3JvD0kWX6YQnPpjE=", 132 | "FunctionName": "hello-test", 133 | "CodeSize": 265356, 134 | "MemorySize": 128, 135 | "FunctionArn": "arn:aws:lambda:eu-west-1:259394719635:function:hello-test", 136 | "Version": "$LATEST", 137 | "Role": "arn:aws:iam::259394719635:role/lambda", 138 | "Timeout": 20, 139 | "LastModified": "2016-05-23T10:32:38.126+0000", 140 | "Handler": "run.handle", 141 | "Runtime": "nodejs4.3", 142 | "Description": "" 143 | } 144 | ``` 145 | 146 | * Run function on Lambda: 147 | 148 | ``` 149 | $ aws lamdba invoke-function --function-name hello-test 150 | { 151 | "StatusCode": 200 152 | } 153 | $ cat test 154 | "child process exited with code 0" 155 | ``` 156 | 157 | The provided `main.hs` simply output its input to its output. There should be an execution trace in the logs hosted on CloudWatch: 158 | 159 | ![](cloudwatch.png) 160 | 161 | ## Manifest 162 | 163 | * `ghc-centos`: Docker container for building Haskell binaries compatible with [Amazon's Linux AMI](http://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html). Does not seem to be a good idea in general as there are quite a few differences between CentOS and Linux AMI, but in practice it kind works... 164 | * `run-tmpl.js`: Template Javascript wrapper to run binary in a child process. The `$$main$$` string should be replaced by the name of the packed executable, 165 | * `test.js`: Javascript test wrapper, invoke the handler simulating what AWS Lambda does 166 | * `stack.yaml`, `main.cabal`, `main.hs`: Basic structure for building Haskell code 167 | 168 | ## References 169 | 170 | * [Running executables in AWS Lambda](http://aws.amazon.com/fr/blogs/compute/running-executables-in-aws-lambda/) 171 | * [Child processes in node](https://nodejs.org/api/child_process.html) 172 | * [AWS Lambda documentation](http://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html) 173 | -------------------------------------------------------------------------------- /System/Config.hs: -------------------------------------------------------------------------------- 1 | module System.Config(MainConfig(..), options) where 2 | 3 | import Data.Text 4 | import Options.Applicative 5 | 6 | data MainConfig = DeleteApi { deleteApiEndpoint :: Text} 7 | | CreateApi { createApiEndpoint :: Text 8 | , lambdaTargetName :: Text 9 | } 10 | | BuildLambda { lambdaTargetName :: Text 11 | , lambdaSrcDirectory :: Text 12 | } 13 | | DeployLambda { lambdaTargetName :: Text } 14 | deriving (Eq,Show,Read) 15 | 16 | mainConfig :: Parser MainConfig 17 | mainConfig = subparser ( 18 | command "api" (info apiConfig 19 | (progDesc "Manipulate AWS API Gateway Endpoints and their relations with Lambda functions")) 20 | <> command "lambda" (info lambdaConfig 21 | (progDesc "Manipulate AWS Lambda functions")) 22 | ) 23 | 24 | apiConfig :: Parser MainConfig 25 | apiConfig = subparser ( 26 | command "create" (info createApiConfig 27 | (progDesc "Create an AWS API Gateway endpoint and ties it to AWS Lambda function")) 28 | <> command "delete" (info deleteApiConfig 29 | (progDesc "Delete an API Gateway endpoint")) 30 | ) 31 | 32 | createApiConfig :: Parser MainConfig 33 | createApiConfig = CreateApi 34 | <$> (pack <$> strOption ( long "endpoint" 35 | <> short 'e' 36 | <> help "Endpoint identifier, a simple string")) 37 | <*> (pack <$> strOption ( long "build-target" 38 | <> short 't' 39 | <> metavar "STRING" 40 | <> help "Target of executable to build and deploy")) 41 | 42 | deleteApiConfig :: Parser MainConfig 43 | deleteApiConfig = DeleteApi 44 | <$> (pack <$> strOption ( long "endpoint" 45 | <> short 'e' 46 | <> help "Endpoint identifier, a simple string. If multiple identifiers match, the first one will be deleted.")) 47 | 48 | lambdaConfig :: Parser MainConfig 49 | lambdaConfig = subparser ( 50 | command "deploy" (info deployLambdaConfig 51 | (progDesc "Deploy an AWS Lambda function package")) 52 | <> command "build" (info buildLambdaConfig 53 | (progDesc "Build an AWS Lambda function package using docker")) 54 | ) 55 | 56 | buildLambdaConfig :: Parser MainConfig 57 | buildLambdaConfig = BuildLambda 58 | <$> (pack <$> strOption ( long "build-target" 59 | <> short 't' 60 | <> metavar "STRING" 61 | <> help "Target of executable to build")) 62 | <*> (pack <$> strOption ( long "source-directory" 63 | <> short 'd' 64 | <> value "." 65 | <> metavar "FILE" 66 | <> help "Source directory of lambda function to build")) 67 | 68 | deployLambdaConfig :: Parser MainConfig 69 | deployLambdaConfig = DeployLambda 70 | <$> (pack <$> strOption ( long "function-name" 71 | <> short 'f' 72 | <> metavar "STRING" 73 | <> help "Function name to deploy")) 74 | 75 | options :: IO MainConfig 76 | options = execParser opts 77 | where 78 | opts = info (helper <*> mainConfig) 79 | ( fullDesc 80 | <> progDesc "Manage AWS Lambda functions" 81 | <> header "Haskell + Lambda" ) 82 | 83 | -------------------------------------------------------------------------------- /cloudwatch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abailly/aws-lambda-haskell/df9afb7498500c2282515752f905a6e05bba9658/cloudwatch.png -------------------------------------------------------------------------------- /example-src/LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abailly/aws-lambda-haskell/df9afb7498500c2282515752f905a6e05bba9658/example-src/LICENSE -------------------------------------------------------------------------------- /example-src/main.cabal: -------------------------------------------------------------------------------- 1 | -- Initial aws-lambda-haskell.cabal generated by cabal init. For further 2 | -- documentation, see http://haskell.org/cabal/users-guide/ 3 | 4 | name: main 5 | version: 0.1.0.0 6 | -- synopsis: 7 | -- description: 8 | -- license: 9 | license-file: LICENSE 10 | author: Arnaud Bailly 11 | maintainer: arnaud.oqube@gmail.com 12 | -- copyright: 13 | -- category: 14 | build-type: Simple 15 | extra-source-files: README.md, README.md~ 16 | cabal-version: >=1.10 17 | 18 | executable main 19 | main-is: main.hs 20 | -- other-modules: 21 | -- other-extensions: 22 | build-depends: base >=4.8 && <4.9 23 | -- hs-source-dirs: 24 | default-language: Haskell2010 25 | -------------------------------------------------------------------------------- /example-src/main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import System.IO 4 | 5 | main :: IO () 6 | main = do 7 | ln <- getLine 8 | putStrLn $ "Got: " ++ ln 9 | 10 | -------------------------------------------------------------------------------- /example-src/stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-5.17 2 | packages: 3 | - '.' 4 | -------------------------------------------------------------------------------- /ghc-centos/Dockerfile: -------------------------------------------------------------------------------- 1 | ## Dockerfile for a haskell environment in CentOS 6 2 | ## Inspired by https://github.com/dimchansky/docker-centos6-haskell/blob/master/7.8.4/Dockerfile 3 | FROM centos:7 4 | MAINTAINER Arnaud Bailly 5 | 6 | ## Install dependencies 7 | RUN yum install -y curl\ 8 | gcc \ 9 | gmp-devel \ 10 | pcre-devel \ 11 | perl \ 12 | tar \ 13 | which \ 14 | xz \ 15 | zlib-devel \ 16 | && yum clean all --releasever=6 \ 17 | && ln -s /lib64/libtinfo.so.5 /lib64/libtinfo.so 18 | 19 | ENV GHC_VERSION=7.10.3 20 | 21 | RUN yum install -y make 22 | 23 | ## Install stack 24 | RUN mkdir -p /opt/stack/bin 25 | RUN curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C /opt/stack/bin '*/stack' 26 | ENV PATH /opt/stack/bin/:$PATH 27 | RUN stack setup $GHC_VERSION 28 | 29 | # application specific stuff 30 | RUN yum install -y lapack-devel blas-devel 31 | -------------------------------------------------------------------------------- /main.cabal: -------------------------------------------------------------------------------- 1 | -- Initial aws-lambda-haskell.cabal generated by cabal init. For further 2 | -- documentation, see http://haskell.org/cabal/users-guide/ 3 | 4 | name: main 5 | version: 0.1.0.0 6 | -- synopsis: 7 | -- description: 8 | -- license: 9 | license-file: LICENSE 10 | author: Arnaud Bailly 11 | maintainer: arnaud.oqube@gmail.com 12 | -- copyright: 13 | -- category: 14 | build-type: Simple 15 | extra-source-files: README.md, README.md~ 16 | cabal-version: >=1.10 17 | 18 | executable main 19 | main-is: main.hs 20 | -- other-modules: 21 | -- other-extensions: 22 | build-depends: base >=4.8 && <4.9, 23 | amazonka, amazonka-apigateway, amazonka-lambda, bytestring, directory, exceptions, filepath, lens, mtl 24 | , optparse-applicative, process, resourcet, system-extra, transformers, text 25 | -- hs-source-dirs: 26 | default-language: Haskell2010 27 | default-extensions: ScopedTypeVariables, OverloadedStrings 28 | ghc-options: -Wall -Werror -threaded 29 | -------------------------------------------------------------------------------- /main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts #-} 2 | {-# LANGUAGE RecordWildCards #-} 3 | module Main where 4 | 5 | import AWS.ApiGateway 6 | import AWS.Lambda 7 | import Control.Lens 8 | import Control.Monad (forM) 9 | import Control.Monad.Trans.AWS 10 | import Data.Functor (void) 11 | import Data.List (isPrefixOf) 12 | import Data.Text (unpack) 13 | import Prelude hiding (null) 14 | import System.Build 15 | import System.Config 16 | import System.Docker 17 | import System.FilePath 18 | import System.IO 19 | import System.IO.Extra 20 | import System.Process 21 | 22 | 23 | main :: IO () 24 | main = options >>= go 25 | 26 | initAWS :: IO Env 27 | initAWS = do 28 | lgr <- newLogger Trace stdout 29 | awsEnv <- newEnv Ireland Discover <&> envLogger .~ lgr 30 | return awsEnv 31 | 32 | go :: MainConfig -> IO () 33 | go CreateApi{..} = initAWS >>= \ awsEnv -> runResourceT (runAWST awsEnv $ createApi createApiEndpoint lambdaTargetName) >>= print 34 | go DeleteApi{..} = initAWS >>= \ awsEnv -> runResourceT (runAWST awsEnv $ deleteApi deleteApiEndpoint) 35 | go BuildLambda{..} = do 36 | -- build docker container 37 | buildDocker 38 | -- build executable with docker 39 | exe <- stackInDocker (ImageName "ghc-centos:lapack") (unpack lambdaSrcDirectory) (unpack lambdaTargetName) 40 | -- extract supplementary libs... 41 | libs <- extractLibs (ImageName "ghc-centos:lapack") (unpack lambdaTargetName) 42 | -- pack executable with js shim in .zip file 43 | packLambda exe (exe:libs) 44 | where 45 | buildDocker :: IO () 46 | buildDocker = callProcess "docker" ["build", "-t", "ghc-centos:lapack","ghc-centos" ] 47 | 48 | packLambda :: FilePath -> [FilePath] -> IO () 49 | packLambda exe files = do 50 | runner <- setMainTo exe <$> readFile "run-tmpl.js" 51 | writeFile "run.js" runner 52 | callProcess "zip" $ [ "lambda.zip", "run.js" ] ++ files 53 | 54 | go DeployLambda{..} = do 55 | awsEnv <- initAWS 56 | createOrUpdateFunction awsEnv lambdaTargetName "lambda.zip" >>= print 57 | where 58 | createOrUpdateFunction awsEnv target zipFile = runResourceT (runAWST awsEnv $ createFunctionWithZip target zipFile) 59 | 60 | setMainTo :: FilePath -> String -> String 61 | setMainTo _ [] = [] 62 | setMainTo exe s | "$$main$$" `isPrefixOf` s = exe ++ setMainTo exe (drop 8 s) 63 | | otherwise = head s : setMainTo exe (tail s) 64 | 65 | 66 | extractLibs :: ImageName -> String -> IO [ FilePath ] 67 | extractLibs (ImageName imgName) targetName = do 68 | cid <- readFile ".cidfile" 69 | stackRoot <- filter (/= '\n') <$> readProcess "docker" [ "run", "--rm", "--volumes-from=" ++ cid, "-w", "/build", imgName, "stack", "path", "--allow-different-user", "--local-install-root" ] "" 70 | libs <- getUnknownLibs <$> readProcess "docker" ["run", "--rm", "--volumes-from=" ++ cid, imgName, "ldd", stackRoot ++ "/bin/" ++ targetName ] "" 71 | forM libs (extractLib cid) 72 | where 73 | extractLib cid lib = do 74 | let targetLib = takeFileName lib 75 | (_, Just hout, _, phdl) <- createProcess $ (proc "docker" ["run", "--rm", "--volumes-from=" ++ cid, imgName, "sh", "-c", "dd if=$(readlink -f " ++ lib ++ ")" ]) { std_out = CreatePipe } 76 | withBinaryFile targetLib WriteMode $ \ hDst -> copy hout hDst 77 | void $ waitForProcess phdl 78 | return targetLib 79 | 80 | 81 | -- | Extract list of non-standard libs to be packaged with executable 82 | -- 83 | -- expect input string to be the result of executing `ldd` on some executable 84 | getUnknownLibs :: String -> [ FilePath ] 85 | getUnknownLibs lddOutput = let mappings = map words $ lines lddOutput 86 | in map (!! 2) $ filter (not . (`elem` standardLibs) . head ) mappings 87 | 88 | -- | List of standard libraries packaged in CentOS image 89 | standardLibs :: [ String ] 90 | standardLibs = [ "linux-vdso.so.1" 91 | , "librt.so.1" 92 | , "libutil.so.1" 93 | , "libdl.so.2" 94 | , "libz.so.1" 95 | , "libgmp.so.10" 96 | , "libm.so.6" 97 | , "libpthread.so.0" 98 | , "libgcc_s.so.1" 99 | , "libc.so.6" 100 | , "/lib64/ld-linux-x86-64.so.2" 101 | ] 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /run-tmpl.js: -------------------------------------------------------------------------------- 1 | const spawn = require('child_process').spawn; 2 | 3 | /** 4 | * handler for AWS Lambda 5 | */ 6 | exports.handle = function(event, context, callback) { 7 | process.env['PATH'] = process.env['PATH'] + ':' + process.env['LAMBDA_TASK_ROOT'] 8 | // help resolve dynamic libraries packaged with function 9 | process.env['LD_LIBRARY_PATH'] = process.env['LAMBDA_TASK_ROOT'] 10 | 11 | const main = spawn('./$$main$$', { stdio: ['pipe', 'pipe', process.stderr] }); 12 | 13 | main.stdout.on('data', function(data) { 14 | console.log('stdout: ' + data); 15 | }); 16 | 17 | main.on('close', function(code) { 18 | console.log('child process pipes closed with code '+ code); 19 | }); 20 | 21 | console.log("sending data to $$main$$:\n" + JSON.stringify(event)); 22 | console.log("$$main$$ pid is " + main.pid); 23 | 24 | main.on('exit', function(code){ 25 | callback(null, 'child process exited with code ' + code); 26 | }); 27 | 28 | main.on('error', function(err) { 29 | console.error('error: ' + err); 30 | callback(err, 'child process exited with error: ' + err); 31 | }); 32 | 33 | main.stdin.write(JSON.stringify({ 34 | 'event': event, 35 | 'context': context 36 | }) + '\n'); 37 | } 38 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-6.2 2 | packages: 3 | - '.' 4 | - location: 5 | git: https://github.com/abailly/system-extra 6 | commit: bbb78d47462a24f3bcfc2bab0ac080370262ea49 7 | extra-dep: true 8 | - location: 9 | git: https://github.com/abailly/hs-word2vec 10 | commit: f3fb0a797056c9ae325367e1035ba4fd16f3b2d3 11 | 12 | extra-deps: 13 | - 'amazonka-core-1.4.3' 14 | - 'amazonka-1.4.3' 15 | - 'amazonka-apigateway-1.4.3' 16 | - 'amazonka-lambda-1.4.3' 17 | - tokenize-0.3.0 -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var echo = require("./run.js"); 2 | 3 | echo.handle({'foo': "foo"}, 4 | {'bar': 12}); 5 | --------------------------------------------------------------------------------