├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── CHANGELOG.md ├── Main.hs ├── Setup.hs ├── default.nix ├── lambda-test.cabal └── nix ├── sources.json └── sources.nix /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # Trigger the workflow on push or pull request, but only for the master branch 4 | on: 5 | pull_request: 6 | push: 7 | branches: [master] 8 | 9 | jobs: 10 | main: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | # Load the source code into CI by checking out the branch 16 | - uses: actions/checkout@v2 17 | 18 | # Install cachix 19 | - uses: cachix/install-nix-action@v10 20 | 21 | # Sign-in to cachix. This allows me to save to cachix 22 | # the binaries created during CI runs 23 | - uses: cachix/cachix-action@v6 24 | with: 25 | name: lazamar 26 | signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' 27 | 28 | # Select the caches I want to use 29 | - run: cachix use lazamar 30 | - run: cachix use static-haskell-nix 31 | 32 | # Build my code and save binaries to my cache if they are not already there 33 | - run: nix-build | cachix push lazamar 34 | 35 | - name: AWS setup 36 | uses: aws-actions/configure-aws-credentials@v1 37 | with: 38 | aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} 39 | aws-secret-access-key: ${{ secrets.AWS_ACCESS_KEY_SECRET }} 40 | aws-region: eu-west-2 41 | 42 | - name: Deploy to Lambda 43 | run: | 44 | aws lambda update-function-code --function-name lambda-test --zip-file fileb://result/function.zip 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | dist* 3 | *~ 4 | result* 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Revision history for lambda-test 2 | 3 | ## 0.1.0.0 -- YYYY-mm-dd 4 | 5 | * First version. Released on an unsuspecting world. 6 | -------------------------------------------------------------------------------- /Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveGeneric #-} 2 | {-# LANGUAGE DeriveAnyClass #-} 3 | {-# LANGUAGE DuplicateRecordFields #-} 4 | {-# LANGUAGE NamedFieldPuns #-} 5 | {-# LANGUAGE OverloadedStrings #-} 6 | 7 | module Main where 8 | 9 | import Aws.Lambda 10 | import Aws.Lambda.Runtime 11 | import GHC.Generics 12 | import Data.Aeson 13 | import Data.Aeson.Encode.Pretty 14 | import Data.Either 15 | import Data.Text (Text) 16 | import Data.Text.Lazy (toStrict) 17 | import Data.Text.Lazy.Encoding (decodeUtf8) 18 | import Network.HTTP.Types.Header 19 | 20 | main :: IO () 21 | main = runLambda run 22 | where 23 | run :: LambdaOptions -> IO (Either a LambdaResult) 24 | run opts = do 25 | result <- either (error . show) id $ handler <$> (decodeObj (eventObject opts)) <*> (decodeObj (contextObject opts)) 26 | either error (pure . Right . StandaloneLambdaResult . encodeObj) result 27 | 28 | type Request = Value 29 | 30 | data Response = Response 31 | { statusCode :: Int 32 | , headers :: [(Text, Text)] 33 | , body :: Text 34 | , isBase64Encoded :: Bool 35 | } deriving (Generic, ToJSON) 36 | 37 | handler :: Request -> Context -> IO (Either String Response) 38 | handler e context = return 39 | $ Right 40 | $ Response 200 mempty (toStrict $ decodeUtf8 $ encodePretty e) False 41 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | # Little snippet from Niv to get our pinned dependencies 2 | { sources ? import ./nix/sources.nix }: 3 | with { overlay = _: pkgs: { niv = import sources.niv {}; }; }; 4 | 5 | let 6 | # We use a nixpkgs version with modified configuration to build static 7 | # executables. From https://github.com/nh2/static-haskell-nix 8 | pkgs = (import (sources.nixpkgs-static + "/survey/default.nix") { 9 | # This is the nixpkgs revision we are modifying. 10 | # We pinned the revision with 'niv add NixOS/nixpkgs -a rev= 11 | normalPkgs = import sources.nixpkgs {}; 12 | }).pkgs; 13 | 14 | # Nix usually takes all files in the source directory into consideration and 15 | # busts the package cache whenever any of these files change. To avoid that 16 | # we use hercules-ci/gitignore.nix so that nix will ignore files included in 17 | # our .gitignore 18 | gitignoreSource = (import sources.gitignore { lib = pkgs.lib; }).gitignoreSource; 19 | 20 | lambda-test = 21 | pkgs.haskell.lib.overrideCabal 22 | # Create a nix derivation from the cabal package 23 | (pkgs.haskellPackages.callCabal2nix "lambda-test" (gitignoreSource ./.) {}) 24 | # Set flags that make sure that we are building a truly static executable 25 | (old: { 26 | configureFlags = [ 27 | "-flambda" 28 | "--ghc-option=-optl=-static" 29 | "--extra-lib-dirs=${pkgs.gmp6.override { withStatic = true; }}/lib" 30 | "--extra-lib-dirs=${pkgs.zlib.static}/lib" 31 | "--extra-lib-dirs=${pkgs.libffi.overrideAttrs (old: { dontDisableStatic = true; })}/lib" 32 | "--disable-executable-stripping" 33 | ]; 34 | }); 35 | 36 | # Create a zip file ready to be sent to AWS lambda 37 | function-zip = pkgs.runCommandNoCC "lambda-test.zip" { buildInputs = [ pkgs.zip ]; } 38 | '' 39 | mkdir -p $out 40 | cp ${lambda-test}/bin/lambda-test bootstrap 41 | zip $out/function.zip bootstrap 42 | ''; 43 | in 44 | { inherit 45 | lambda-test 46 | function-zip ; 47 | } 48 | -------------------------------------------------------------------------------- /lambda-test.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | name: lambda-test 3 | version: 0.1.0.0 4 | author: Marcelo Lazaroni 5 | build-type: Simple 6 | 7 | executable lambda-test 8 | main-is: Main.hs 9 | build-depends: 10 | base 11 | , aeson 12 | , aeson-pretty 13 | , text 14 | , aws-lambda-haskell-runtime >= 2.0.1 && < 3.0.0 15 | , http-types 16 | default-language: Haskell2010 17 | -------------------------------------------------------------------------------- /nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "aws-lambda-haskell-runtime": { 3 | "branch": "master", 4 | "description": "⚡Haskell runtime for AWS Lambda", 5 | "homepage": "https://theam.github.io/aws-lambda-haskell-runtime/", 6 | "owner": "theam", 7 | "repo": "aws-lambda-haskell-runtime", 8 | "rev": "7bdd808d0a1e46ce2d15e09ddf295bbb87c3ad2a", 9 | "sha256": "0nlzdd4wb90gcsn50y243xk1lx02wafk0wf5jcnf8ckr5arh05vp", 10 | "type": "tarball", 11 | "url": "https://github.com/theam/aws-lambda-haskell-runtime/archive/7bdd808d0a1e46ce2d15e09ddf295bbb87c3ad2a.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | }, 14 | "gitignore": { 15 | "branch": "master", 16 | "description": "Nix function for filtering local git sources", 17 | "homepage": "", 18 | "owner": "hercules-ci", 19 | "repo": "gitignore", 20 | "rev": "647d0821b590ee96056f4593640534542d8700e5", 21 | "sha256": "0ks37vclz2jww9q0fvkk9jyhscw0ial8yx2fpakra994dm12yy1d", 22 | "type": "tarball", 23 | "url": "https://github.com/hercules-ci/gitignore/archive/647d0821b590ee96056f4593640534542d8700e5.tar.gz", 24 | "url_template": "https://github.com///archive/.tar.gz" 25 | }, 26 | "niv": { 27 | "branch": "master", 28 | "description": "Easy dependency management for Nix projects", 29 | "homepage": "https://github.com/nmattia/niv", 30 | "owner": "nmattia", 31 | "repo": "niv", 32 | "rev": "f73bf8d584148677b01859677a63191c31911eae", 33 | "sha256": "0jlmrx633jvqrqlyhlzpvdrnim128gc81q5psz2lpp2af8p8q9qs", 34 | "type": "tarball", 35 | "url": "https://github.com/nmattia/niv/archive/f73bf8d584148677b01859677a63191c31911eae.tar.gz", 36 | "url_template": "https://github.com///archive/.tar.gz" 37 | }, 38 | "nixpkgs": { 39 | "branch": "master", 40 | "description": "Nix Packages collection", 41 | "homepage": null, 42 | "owner": "NixOS", 43 | "repo": "nixpkgs", 44 | "rev": "dcb64ea42e64aaecd8e6fef65cc86245c9666818", 45 | "sha256": "0i77sgs0gic6pwbkvk9lbpfshgizdrqyh18law2ji1409azc09w0", 46 | "type": "tarball", 47 | "url": "https://github.com/NixOS/nixpkgs/archive/dcb64ea42e64aaecd8e6fef65cc86245c9666818.tar.gz", 48 | "url_template": "https://github.com///archive/.tar.gz" 49 | }, 50 | "nixpkgs-static": { 51 | "branch": "master", 52 | "description": "easily build most Haskell programs into fully static Linux executables", 53 | "homepage": "", 54 | "owner": "nh2", 55 | "repo": "static-haskell-nix", 56 | "rev": "dbce18f4808d27f6a51ce31585078b49c86bd2b5", 57 | "sha256": "084hxnrywsgb73zr41argdkbhkxzm1rqn058pv1l4cp9g1gjr2rr", 58 | "type": "tarball", 59 | "url": "https://github.com/nh2/static-haskell-nix/archive/dbce18f4808d27f6a51ce31585078b49c86bd2b5.tar.gz", 60 | "url_template": "https://github.com///archive/.tar.gz" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /nix/sources.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by Niv. 2 | 3 | let 4 | 5 | # 6 | # The fetchers. fetch_ fetches specs of type . 7 | # 8 | 9 | fetch_file = pkgs: spec: 10 | if spec.builtin or true then 11 | builtins_fetchurl { inherit (spec) url sha256; } 12 | else 13 | pkgs.fetchurl { inherit (spec) url sha256; }; 14 | 15 | fetch_tarball = pkgs: spec: 16 | if spec.builtin or true then 17 | builtins_fetchTarball { inherit (spec) url sha256; } 18 | else 19 | pkgs.fetchzip { inherit (spec) url sha256; }; 20 | 21 | fetch_git = spec: 22 | builtins.fetchGit { url = spec.repo; inherit (spec) rev ref; }; 23 | 24 | fetch_builtin-tarball = spec: 25 | builtins.trace 26 | '' 27 | WARNING: 28 | The niv type "builtin-tarball" will soon be deprecated. You should 29 | instead use `builtin = true`. 30 | 31 | $ niv modify -a type=tarball -a builtin=true 32 | '' 33 | builtins_fetchTarball { inherit (spec) url sha256; }; 34 | 35 | fetch_builtin-url = spec: 36 | builtins.trace 37 | '' 38 | WARNING: 39 | The niv type "builtin-url" will soon be deprecated. You should 40 | instead use `builtin = true`. 41 | 42 | $ niv modify -a type=file -a builtin=true 43 | '' 44 | (builtins_fetchurl { inherit (spec) url sha256; }); 45 | 46 | # 47 | # Various helpers 48 | # 49 | 50 | # The set of packages used when specs are fetched using non-builtins. 51 | mkPkgs = sources: 52 | let 53 | sourcesNixpkgs = 54 | import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) {}; 55 | hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; 56 | hasThisAsNixpkgsPath = == ./.; 57 | in 58 | if builtins.hasAttr "nixpkgs" sources 59 | then sourcesNixpkgs 60 | else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then 61 | import {} 62 | else 63 | abort 64 | '' 65 | Please specify either (through -I or NIX_PATH=nixpkgs=...) or 66 | add a package called "nixpkgs" to your sources.json. 67 | ''; 68 | 69 | # The actual fetching function. 70 | fetch = pkgs: name: spec: 71 | 72 | if ! builtins.hasAttr "type" spec then 73 | abort "ERROR: niv spec ${name} does not have a 'type' attribute" 74 | else if spec.type == "file" then fetch_file pkgs spec 75 | else if spec.type == "tarball" then fetch_tarball pkgs spec 76 | else if spec.type == "git" then fetch_git spec 77 | else if spec.type == "builtin-tarball" then fetch_builtin-tarball spec 78 | else if spec.type == "builtin-url" then fetch_builtin-url spec 79 | else 80 | abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; 81 | 82 | # Ports of functions for older nix versions 83 | 84 | # a Nix version of mapAttrs if the built-in doesn't exist 85 | mapAttrs = builtins.mapAttrs or ( 86 | f: set: with builtins; 87 | listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) 88 | ); 89 | 90 | # fetchTarball version that is compatible between all the versions of Nix 91 | builtins_fetchTarball = { url, sha256 }@attrs: 92 | let 93 | inherit (builtins) lessThan nixVersion fetchTarball; 94 | in 95 | if lessThan nixVersion "1.12" then 96 | fetchTarball { inherit url; } 97 | else 98 | fetchTarball attrs; 99 | 100 | # fetchurl version that is compatible between all the versions of Nix 101 | builtins_fetchurl = { url, sha256 }@attrs: 102 | let 103 | inherit (builtins) lessThan nixVersion fetchurl; 104 | in 105 | if lessThan nixVersion "1.12" then 106 | fetchurl { inherit url; } 107 | else 108 | fetchurl attrs; 109 | 110 | # Create the final "sources" from the config 111 | mkSources = config: 112 | mapAttrs ( 113 | name: spec: 114 | if builtins.hasAttr "outPath" spec 115 | then abort 116 | "The values in sources.json should not have an 'outPath' attribute" 117 | else 118 | spec // { outPath = fetch config.pkgs name spec; } 119 | ) config.sources; 120 | 121 | # The "config" used by the fetchers 122 | mkConfig = 123 | { sourcesFile ? ./sources.json 124 | , sources ? builtins.fromJSON (builtins.readFile sourcesFile) 125 | , pkgs ? mkPkgs sources 126 | }: rec { 127 | # The sources, i.e. the attribute set of spec name to spec 128 | inherit sources; 129 | 130 | # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers 131 | inherit pkgs; 132 | }; 133 | in 134 | mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } 135 | --------------------------------------------------------------------------------