packages feed

stratosphere-0.9.0: 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
  & agraName ?~ "myApi"

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


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

  where
    integration =
      apiGatewayMethodIntegration
      & agmiType ?~ Literal AWS
      & agmiIntegrationHttpMethod ?~ Literal POST
      & agmiUri ?~ Join "" [
          "arn:aws:apigateway:"
        , Ref "AWS::Region"
        , ":lambda:path/2015-03-31/functions/"
        , GetAtt "ReadTableLambda" "Arn"
        , "/invocations"]
      & agmiIntegrationResponses ?~ [ integrationResponse ]
      & agmiPassthroughBehavior ?~ Literal WHEN_NO_TEMPLATES
      & agmiRequestTemplates ?~ [ ]

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

    methodResponse =
      apiGatewayMethodMethodResponse
      "200"

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

  where
    integration =
      apiGatewayMethodIntegration
      & agmiType ?~ Literal AWS
      & agmiIntegrationHttpMethod ?~ Literal POST
      & agmiUri ?~ Join "" [
          "arn:aws:apigateway:"
        , Ref "AWS::Region"
        , ":lambda:path/2015-03-31/functions/"
        , GetAtt "WriteTableLambda" "Arn"
        , "/invocations"]
      & agmiIntegrationResponses ?~ [integrationResponse]
      & agmiPassthroughBehavior ?~ Literal WHEN_NO_TEMPLATES
      & agmiRequestTemplates ?~ [ ("application/json", "{\"body\": $input.body}") ]

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

    methodResponse =
      apiGatewayMethodMethodResponse
      "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")
    (Literal 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")
    (Literal 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 =
      iamRolePolicy
      [ ("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 =
      iamRolePolicy
      [ ("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
    keySchema
    provisionedThroughput
  & ddbtAttributeDefinitions ?~ attributeDefinitions
  & ddbtTableName ?~ "stratosphere-dynamodb-table"

  where
    attributeDefinitions = [
        dynamoDBTableAttributeDefinition
        (Literal "Id")
        (Literal S)
      ]
    keySchema = [
        dynamoDBTableKeySchema
        (Literal "Id")
        (Literal HASH)
      ]
    provisionedThroughput =
      dynamoDBTableProvisionedThroughput
      (Literal 1)
      (Literal 1)

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