packages feed

stratosphere-0.2.1: examples/apigw-lambda-dynamodb.hs

{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}

module Main where

import Control.Lens
import Data.Aeson (Value (Array), object)
import qualified Data.ByteString.Lazy.Char8 as B
import Data.Text (Text)
import Stratosphere


-- to curl the endpoints: (substitute your APIGW deployment URL)
-- curl -v -X POST -H "Content-Type: application/json" -d "{\"property\": 3}" "https://k99jn3an8a.execute-api.us-east-1.amazonaws.com/v1/thing"
-- curl -v "https://k99jn3an8a.execute-api.us-east-1.amazonaws.com/v1/thing"

main :: IO ()
main = B.putStrLn $ encodeTemplate myTemplate

myTemplate :: Template
myTemplate = template
  [ readLambdaRole
  , writeLambdaRole
  , readLambda
  , writeLambda
  , readLambdaPermission
  , writeLambdaPermission
  , dynamoDbTable
  , apiGWRestApi
  , apiGWResource
  , getMethod
  , postMethod
  , apiGWDeployment
  ]
  & description ?~ "Simple restful API gateway attached to a Lambda that reads from and writes the request body to a DynamoDB table"
  & formatVersion ?~ "2010-09-09"

apiGWRestApi :: Resource
apiGWRestApi = (resource "ApiGWRestApi" $
  ApiGatewayRestApiProperties $
  apiGatewayRestApi
    "myApi"
  )

apiGWResource :: Resource
apiGWResource = (resource "ApiGWResource" $
  ApiGatewayResourceProperties $
  apiGatewayResource
    (GetAtt "ApiGWRestApi" "RootResourceId")
    "thing"
    (toRef apiGWRestApi)
  )

getMethod :: Resource
getMethod = (resource "ApiGWGetMethod" $
  ApiGatewayMethodProperties $
  apiGatewayMethod
    NONE
    GET
    (toRef apiGWResource)
    (toRef apiGWRestApi)
    & agmeIntegration ?~ integration
    & agmeMethodResponses ?~ [ methodResponse ]
  )
  & dependsOn ?~ deps [
      readLambdaPermission
    ]

  where
    integration = apiGatewayIntegration AWS
      & agiIntegrationHttpMethod ?~ POST
      & agiUri ?~ (Join "" [
          "arn:aws:apigateway:"
        , Ref "AWS::Region"
        , ":lambda:path/2015-03-31/functions/"
        , GetAtt "ReadTableLambda" "Arn"
        , "/invocations"])
      & agiIntegrationResponses ?~ [ integrationResponse ]
      & agiPassthroughBehavior ?~ WHEN_NO_TEMPLATES
      & agiRequestTemplates ?~ [ ]

    integrationResponse = apiGatewayIntegrationResponse
      & agirResponseTemplates ?~ [ ("application/json", "$input.json('$.body')") ]
      & agirStatusCode ?~ "200"

    methodResponse = apiGatewayMethodResponse "200"


postMethod :: Resource
postMethod = (resource "ApiGWPutMethod" $
  ApiGatewayMethodProperties $
  apiGatewayMethod
    NONE
    POST
    (toRef apiGWResource)
    (toRef apiGWRestApi)
    & agmeIntegration ?~ integration
    & agmeMethodResponses ?~ [ methodResponse ]
  )
  & dependsOn ?~ deps [
      writeLambdaPermission
    ]

  where
    integration = apiGatewayIntegration AWS
      & agiIntegrationHttpMethod ?~ POST
      & agiUri ?~ (Join "" [
          "arn:aws:apigateway:"
        , Ref "AWS::Region"
        , ":lambda:path/2015-03-31/functions/"
        , GetAtt "WriteTableLambda" "Arn"
        , "/invocations"])
      & agiIntegrationResponses ?~ [ integrationResponse ]
      & agiPassthroughBehavior ?~ WHEN_NO_TEMPLATES
      & agiRequestTemplates ?~ [ ("application/json", "{\"body\": $input.body}") ]

    integrationResponse = apiGatewayIntegrationResponse
      & agirResponseTemplates ?~ [ ("application/json", "$input.json('$.body')") ]
      & agirStatusCode ?~ "200"

    methodResponse = apiGatewayMethodResponse "200"

apiGWDeployment :: Resource
apiGWDeployment = (resource "ApiGWDeployment" $
  ApiGatewayDeploymentProperties $
  apiGatewayDeployment
    (toRef apiGWRestApi)
    & agdStageName ?~ "v1"
  )
  & dependsOn ?~ deps [
      apiGWResource
    , getMethod
    , postMethod
    ]

readLambda :: Resource
readLambda = (resource "ReadTableLambda" $
  LambdaFunctionProperties $
  lambdaFunction
    lambdaCode
    "index.handler"
    (GetAtt "ReadLambdaRole" "Arn")
    NodeJS43
    & lfFunctionName ?~ "readTable"
  )

  where
    lambdaCode :: LambdaFunctionCode
    lambdaCode = lambdaFunctionCode
      & lfcZipFile ?~ code

    code :: Val Text
    code = "\
    \ var AWS = require('aws-sdk');\n\
    \ var awsConfig = { region: 'us-east-1' }; \n\
    \ AWS.config.apiVersions = { dynamodb: '2012-08-10' }; \n\
    \ var dynamoDoc = new AWS.DynamoDB.DocumentClient(awsConfig); \n\
    \ exports.handler = function(event, context, callback) {\n\
    \   console.log(JSON.stringify(event));\n\
    \   var params = { TableName : 'stratosphere-dynamodb-table', Key: { Id: 'abc123' } }; \n\
    \   dynamoDoc.get(params, function(err, data) { \n\
    \     if (err) { \n\
    \       console.log(\"Error:\", err); \n\
    \       callback(err) \n\
    \     } else { \n\
    \       callback(null, {\"body\": data.Item.Data});\n\
    \     } \n\
    \   });\n\
    \ }\n\
    \ "


writeLambda :: Resource
writeLambda = (resource "WriteTableLambda" $
  LambdaFunctionProperties $
  lambdaFunction
    lambdaCode
    "index.handler"
    (GetAtt "WriteLambdaRole" "Arn")
    NodeJS43
    & lfFunctionName ?~ "writeTable"
  )

  where
    lambdaCode :: LambdaFunctionCode
    lambdaCode = lambdaFunctionCode
      & lfcZipFile ?~ code

    code :: Val Text
    code = "\
    \ var AWS = require('aws-sdk');\n\
    \ var awsConfig = { region: 'us-east-1' }; \n\
    \ AWS.config.apiVersions = { dynamodb: '2012-08-10' }; \n\
    \ var dynamoDoc = new AWS.DynamoDB.DocumentClient(awsConfig); \n\
    \ exports.handler = function(event, context, callback) {\n\
    \   console.log(JSON.stringify(event));\n\
    \   var params = { TableName : 'stratosphere-dynamodb-table', Item: { Id: 'abc123', Data: event.body } }; \n\
    \   dynamoDoc.put(params, function(err, data) { \n\
    \     if (err) { \n\
    \       console.log(\"Error:\", err); \n\
    \       callback(err) \n\
    \     } else { \n\
    \       callback(null, {\"body\": 'success!'});\n\
    \     } \n\
    \   });\n\
    \ }\n\
    \ "

readLambdaRole :: Resource
readLambdaRole = resource "ReadLambdaRole" $
  IAMRoleProperties $
  iamRole rolePolicyDocumentObject
  & iamrPolicies ?~ [ executePolicy ]
  & iamrRoleName ?~ "ReadLambdaRole"
  & iamrPath ?~ "/"

  where
    executePolicy =
      iamPolicies
      [ ("Version", "2012-10-17")
      , ("Statement", statement)
      ]
      "MyLambdaExecutionPolicy"

      where
        statement = object
          [ ("Effect", "Allow")
          , ("Action", actions)
          , ("Resource", "*")
          ]

        actions = Array
          [ "logs:CreateLogGroup"
          , "logs:CreateLogStream"
          , "logs:PutLogEvents"
          , "dynamodb:GetItem"
          ]


    rolePolicyDocumentObject =
      [ ("Version", "2012-10-17")
      , ("Statement", statement)
      ]

      where
        statement = object
          [ ("Effect", "Allow")
          , ("Principal", principal)
          , ("Action", "sts:AssumeRole")
          ]

        principal = object
          [ ("Service", "lambda.amazonaws.com") ]

writeLambdaRole :: Resource
writeLambdaRole = resource "WriteLambdaRole" $
  IAMRoleProperties $
  iamRole rolePolicyDocumentObject
  & iamrPolicies ?~ [ executePolicy ]
  & iamrRoleName ?~ "WriteLambdaRole"
  & iamrPath ?~ "/"

  where
    executePolicy =
      iamPolicies
      [ ("Version", "2012-10-17")
      , ("Statement", statement)
      ]
      "MyLambdaExecutionPolicy"

      where
        statement = object
          [ ("Effect", "Allow")
          , ("Action", actions)
          , ("Resource", "*")
          ]

        actions = Array
          [ "logs:CreateLogGroup"
          , "logs:CreateLogStream"
          , "logs:PutLogEvents"
          , "dynamodb:PutItem"
          ]


    rolePolicyDocumentObject =
      [ ("Version", "2012-10-17")
      , ("Statement", statement)
      ]

      where
        statement = object
          [ ("Effect", "Allow")
          , ("Principal", principal)
          , ("Action", "sts:AssumeRole")
          ]

        principal = object
          [ ("Service", "lambda.amazonaws.com") ]

readLambdaPermission :: Resource
readLambdaPermission = resource "ReadLambdaPermission" $
  LambdaPermissionProperties $
  lambdaPermission
    "lambda:*"
    (GetAtt "ReadTableLambda" "Arn")
    "apigateway.amazonaws.com"

writeLambdaPermission :: Resource
writeLambdaPermission = resource "WriteLambdaPermission" $
  LambdaPermissionProperties $
  lambdaPermission
    "lambda:*"
    (GetAtt "WriteTableLambda" "Arn")
    "apigateway.amazonaws.com"

dynamoDbTable :: Resource
dynamoDbTable = resource "Table" $
  DynamoDBTableProperties $
  dynamoDBTable
    attributeDefinitions
    keySchema
    provisionedThroughput
  & ddbtTableName ?~ "stratosphere-dynamodb-table"

  where
    attributeDefinitions = [
        dynamoDBAttributeDefinition "Id" S
      ]
    keySchema = [
        dynamoDBKeySchema "Id" HASH
      ]
    provisionedThroughput =
      dynamoDBProvisionedThroughput (Literal 1) (Literal 1)

deps :: [Resource] -> [Text]
deps = map (\d -> d ^. resName)