├── .gitignore ├── Makefile ├── README.md ├── docker-lambdar ├── Dockerfile └── README.md ├── lambdar.js ├── lambdar.mk └── test-r.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | lambdar.zip 3 | lambdar.zip*.json 4 | r-*.tar.gz 5 | r/ 6 | test-r-interactive.sh 7 | .Rhistory 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Lambdar: Build R for Amazon Linux and deploy to AWS Lambda 2 | # 3 | # Usage: 4 | # make docker - build AWS Lambda compatible R Docker container 5 | # make lambdar - build minimal r-$(R_VERSION).tar.gz for AWS Lambda 6 | # make test - quick check that the built R version works 7 | # make create - Create AWS Lambda function from local ZIP file 8 | # make deploy - Update AWS Lambda function from local ZIP file 9 | # 10 | # The Docker container henrikbengtsson/lambdar:build is defined by 11 | # the docker-lambdar/Dockerfile file. 12 | name=lambdar 13 | 14 | R_VERSION=3.5.0 15 | 16 | # R must be built on a system compatible with Amazon Linux with glibc <= 2.17. 17 | glibc_version=$(shell ldd --version | head -1 | sed -E 's/.*GLIBC[[:space:]]([0-9.-]+).*/\1/g' | tr - .) 18 | glibc_version_smallest=$(shell printf "$(glibc_version)\n2.17" | sort -V | head -1) 19 | #ifeq ($(glibc_version_smallest), 2.17) 20 | # $(error "ERROR: R must be built with GLIBC (<= 2.17) in order to work on AWS Lambda: $(glibc_version)") 21 | #endif 22 | 23 | .DELETE_ON_ERROR: 24 | .SECONDARY: 25 | 26 | all: docker lambdar 27 | 28 | lambdar: $(name).zip 29 | 30 | docker: 31 | docker build -t henrikbengtsson/lambdar:build docker-lambdar/ 32 | 33 | debug: 34 | @echo "R version: $(R_VERSION)" 35 | @echo "GLIBC version: $(glibc_version)" 36 | 37 | create: $(name).zip.create.json 38 | 39 | deploy: $(name).zip.deploy.json 40 | 41 | # Bundle up R and all of its dependencies 42 | r-%.tar.gz: lambdar.mk 43 | docker run -v $(PWD):/xfer -w /xfer henrikbengtsson/lambdar:build make -f lambdar.mk 44 | 45 | test: r-$(R_VERSION).tar.gz 46 | docker run -it --env INTERACTIVE=true --env R_VERSION=$(R_VERSION) -v $(PWD):/xfer -w /xfer henrikbengtsson/lambdar:build bash -C test-r.sh 47 | 48 | # Build the zip archive for AWS Lambda 49 | %.zip: %.js r-$(R_VERSION).tar.gz 50 | zip -qr $@ $^ 51 | 52 | # Create Lambda function from local ZIP (function name must not contain periods) 53 | # Required environment variables: 54 | # * AWS_LAMBDA_ROLE_ARN ="arn:aws:iam:::role/service-role/" 55 | %.zip.create.json: %.zip 56 | aws lambda create-function --runtime nodejs8.10 --handler $*.handler --timeout 10 --role $$AWS_LAMBDA_ROLE_ARN --function-name $*-$(subst .,_,$(R_VERSION)) --description "R $(R_VERSION)" --zip-file fileb://$< > $@ 57 | 58 | # Update the Lambda function from local ZIP 59 | %.zip.deploy.json: %.zip 60 | aws lambda update-function-code --function-name $*-$(subst .,_,$(R_VERSION)) --zip-file fileb://$< > $@ 61 | 62 | clean: 63 | @rm -f r-$(R_VERSION).tar.gz 64 | @rm -f $(name).zip 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ƛR: Lambdar 2 | 3 | Run R on AWS Lambda. 4 | 5 | # tl;dr 6 | 7 | 8 | 9 | ``` 10 | The decimal point is at the | 11 | 12 | -2 | 3 13 | -1 | 87666 14 | -1 | 333332210 15 | -0 | 999988888655 16 | -0 | 444433333222222111110 17 | 0 | 00001111111112223344 18 | 0 | 5556667777889999 19 | 1 | 00111123344 20 | 1 | 556 21 | 2 | 14 22 | ``` 23 | 24 | # Code 25 | 26 | See the Lambda function [lambdar.js](lambdar.js) and [Makefile](Makefile). 27 | 28 | # API Examples 29 | 30 | ### HTTP GET 31 | 32 | ``` 33 | ❯❯❯ curl -L 'http://lambdar.sjackman.ca/?e=355/113' 34 | [1] 3.141593 35 | ``` 36 | 37 | ``` 38 | ❯❯❯ curl 'https://8nhzfyyq66.execute-api.us-west-2.amazonaws.com/prod/lambdar?e=355/113' 39 | [1] 3.141593 40 | ``` 41 | 42 | ### HTTP PUT 43 | 44 | ``` 45 | ❯❯❯ curl -d '355/113' https://8nhzfyyq66.execute-api.us-west-2.amazonaws.com/prod/lambdar 46 | [1] 3.141593 47 | ``` 48 | 49 | Sadly, HTTP PUT does not work with the redirected URL . 50 | -------------------------------------------------------------------------------- /docker-lambdar/Dockerfile: -------------------------------------------------------------------------------- 1 | ## Build usage: 2 | ## docker build -t henrikbengtsson/lambdar:build . 3 | ## 4 | ## Run usage: 5 | ## docker run -i -t henrikbengtsson/lambdar:build bash 6 | ## 7 | ## Source: https://github.com/{HenrikBengtsson|sjackman}/lambdar 8 | ## Copyright: Henrik Bengtsson (2017-2018) 9 | ## License: GPL (>= 2.1) [https://www.gnu.org/licenses/gpl.html] 10 | 11 | FROM lambci/lambda:build 12 | 13 | MAINTAINER Henrik Bengtsson "henrikb@braju.com" 14 | 15 | RUN yum install -y gcc-gfortran 16 | RUN yum install -y libgfortran # Otherwise ld error duing 'make' 17 | RUN yum install -y readline-devel # Required iff --with-readline=yes 18 | RUN yum install -y bzip2-devel 19 | RUN yum install -y xz-devel # liblzma 20 | RUN yum install -y pcre-devel 21 | RUN yum install -y curl-devel 22 | 23 | ## Optional 24 | RUN yum install -y libpng-devel 25 | 26 | ENV R_VERSION=3.5.0 27 | ENV R_READLINE=yes 28 | ENV R_PREFIX=/tmp/r-local/${R_VERSION} 29 | 30 | ## Build and install R from source 31 | RUN cd /tmp; curl -O https://cloud.r-project.org/src/base/R-3/R-${R_VERSION}.tar.gz 32 | RUN cd /tmp; tar -zxf R-${R_VERSION}.tar.gz 33 | RUN cd /tmp/R-${R_VERSION}; ./configure --with-readline=${R_READLINE} --without-x --without-libtiff --without-jpeglib --without-cairo --without-lapack --without-ICU --without-recommended-packages --disable-R-profiling --disable-java --disable-nls --prefix=${R_PREFIX} 34 | RUN cd /tmp/R-${R_VERSION}; make 35 | RUN cd /tmp/R-${R_VERSION}; make install 36 | 37 | ## R runtime properties 38 | RUN mkdir ${R_PREFIX}/lib64/R/site-library ## Where to install packages 39 | 40 | ENV PATH=${R_PREFIX}/bin:${PATH} 41 | ENV R_BIOC_VERSION=3.7 42 | RUN echo "R_BIOC_VERSION=${R_BIOC_VERSION}" >> ~/.Renviron 43 | RUN echo "options(repos = c(CRAN='https://cloud.r-project.org', BioCsoft='https://bioconductor.org/packages/${R_BIOC_VERSION}/bioc', BioCann='https://bioconductor.org/packages/${R_BIOC_VERSION}/data/annotation', BioCexp='https://bioconductor.org/packages/${R_BIOC_VERSION}/data/experiment'))" >> ~/.Rprofile 44 | -------------------------------------------------------------------------------- /docker-lambdar/README.md: -------------------------------------------------------------------------------- 1 | # Short Description 2 | 3 | Amazon AWS Lambda environment extended with a build environment containing a minimalistic R 4 | 5 | 6 | # Full Description 7 | 8 | The [lambci/lambda](https://hub.docker.com/r/lambci/lambda/) images reproduce the Amazon AWS Lambda environment as far as possible. This image ([Dockerfile](https://github.com/HenrikBengtsson/lambdar)) extends their `lambci/lambda:build` image by providing a minimalistic [R](https://www.r-project.org/) installation. 9 | 10 | Note that this is a _build environment_ containing a minimalistic R installation. It requires more work in order to deploy the R software on AWS Lambda. 11 | -------------------------------------------------------------------------------- /lambdar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const spawnSync = require('child_process').spawnSync; 5 | 6 | /** The version of R */ 7 | const version = '3.5.0'; 8 | 9 | function spawn(command, args) { 10 | const output = spawnSync(command, args, { 11 | env: { 12 | HOME: process.cwd(), 13 | LD_LIBRARY_PATH: '/tmp/r/${version}/lib64/R/lib' 14 | } 15 | }); 16 | if (output.error != null) { 17 | console.log(output); 18 | return output.error.toString(); 19 | } 20 | const s = output.stdout.toString() + output.stderr.toString(); 21 | console.log(s); 22 | return s; 23 | } 24 | 25 | function install_r() { 26 | if (fs.existsSync('/tmp/r')) 27 | return; 28 | spawn('tar', ['xf', `/var/task/r-${version}.tar.gz`, '-C', '/tmp']); 29 | } 30 | 31 | function chdir_home() { 32 | const path = `/tmp/${process.pid}.${Math.random()}`; 33 | fs.mkdirSync(path); 34 | process.env.HOME = path; 35 | process.chdir(path); 36 | } 37 | 38 | function eval_r(expr) { 39 | chdir_home(); 40 | return spawn(`/tmp/r/${version}/bin/Rscript`, ['-e', expr]) 41 | } 42 | 43 | /** 44 | * Transfer bottles from CircleCI to BinTray and GitHub 45 | */ 46 | exports.handler = (event, context, callback) => { 47 | console.log('Received event:', JSON.stringify(event, null, 2)); 48 | 49 | const done = (err, res) => callback(null, { 50 | statusCode: err ? '400' : '200', 51 | body: err ? err.message : res, 52 | headers: { 53 | 'Content-Type': 'text/plain', 54 | }, 55 | }); 56 | 57 | const redirect = (url) => callback(null, { 58 | statusCode: 303, 59 | headers: { 'Content-Type': 'text/html', 'Location': url }, 60 | body: `You are being redirected.` 61 | }); 62 | 63 | switch (event.httpMethod) { 64 | case 'GET': 65 | if (event.queryStringParameters == null || !('e' in event.queryStringParameters)) { 66 | redirect('https://github.com/sjackman/lambdar'); 67 | break; 68 | } 69 | case 'POST': 70 | const expr = event.httpMethod == "GET" ? event.queryStringParameters.e : event.body; 71 | console.log(expr); 72 | install_r(); 73 | done(null, eval_r(expr)); 74 | break; 75 | default: 76 | done(new Error(`lambdar: Unsupported HTTP method "${event.httpMethod}"`)); 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /lambdar.mk: -------------------------------------------------------------------------------- 1 | # lambdar: Build R to run on AWS Lambda 2 | 3 | R_VERSION=3.5.0 4 | R_REPOS=https://cloud.r-project.org 5 | 6 | R_PREFIX=/tmp/r/$(R_VERSION) 7 | 8 | .DELETE_ON_ERROR: 9 | .SECONDARY: 10 | 11 | all: r-$(R_VERSION).tar.gz 12 | 13 | # Build R from source 14 | /tmp/r/%: /tmp/R-% 15 | cd /tmp/R-$*;\ 16 | ./configure --with-readline=${R_READLINE} --without-x --without-libtiff --without-jpeglib --without-cairo --without-lapack --without-ICU --without-recommended-packages --disable-R-profiling --disable-java --disable-nls --prefix=${R_PREFIX}; \ 17 | make; \ 18 | make install 19 | 20 | # Install custom packages here, if any 21 | pkgs: /tmp/r/$(R_VERSION) 22 | # $(R_PREFIX)/bin/Rscript -e "install.packages(c('future'), repos ='$(R_REPOS)')" 23 | 24 | prune: /tmp/r/$(R_VERSION) 25 | @rm -rf $(R_PREFIX)/share 26 | @rm -f $(R_PREFIX)/lib64/R/COPYING $(R_PREFIX)/lib64/R/SVN-REVISION 27 | @rm -rf $(R_PREFIX)/lib64/R/doc/* 28 | @mkdir -p /tmp/r/$(R_VERSION)/lib64/R/doc/html ## install.packages() 29 | @rm -f $(R_PREFIX)/lib64/R/doc/NEWS* 30 | @rm -rf $(R_PREFIX)/lib64/R/doc/manual* 31 | @rm -rf $(R_PREFIX)/lib64/R/doc 32 | @rm -rf $(R_PREFIX)/lib64/R/share/licenses/ 33 | @rm -f $(R_PREFIX)/lib64/R/library/*/INDEX 34 | @rm -rf $(R_PREFIX)/lib64/R/library/*/demo 35 | @rm -f $(R_PREFIX)/lib64/R/library/*/Meta/demo.rds 36 | @rm -f $(R_PREFIX)/lib64/R/library/*/Meta/hsearch.rds 37 | @rm -f $(R_PREFIX)/lib64/R/library/*/Meta/links.rds 38 | @rm -f $(R_PREFIX)/lib64/R/library/*/Meta/Rd.rds 39 | @rm -f $(R_PREFIX)/lib64/R/library/*/Meta/vignette.rds 40 | @rm -rf $(R_PREFIX)/lib64/R/library/*/doc 41 | @rm -rf $(R_PREFIX)/lib64/R/library/*/html 42 | @rm -rf $(R_PREFIX)/lib64/R/library/*/help 43 | @rm -rf $(R_PREFIX)/lib64/R/library/translations 44 | @ln -sf $(R_PREFIX)/lib64/R/bin/R $(R_PREFIX)/bin/R 45 | @ln -sf $(R_PREFIX)/lib64/R/bin/Rscript $(R_PREFIX)/bin/Rscript 46 | @rm -f $(R_PREFIX)/lib64/R/library/*/GPL* 47 | @rm -f $(R_PREFIX)/lib64/R/library/*/NEWS* 48 | @rm -f $(R_PREFIX)/lib64/R/library/*/CHANGELOG* 49 | @rm -f $(R_PREFIX)/lib64/R/library/*/README* 50 | @rm -rf $(R_PREFIX)/lib64/R/library/*/vignettes* 51 | 52 | 53 | # Copy dependencies from amazonlinux-minimal-r build 54 | lib/%: /tmp/r/$(R_VERSION) 55 | cp $(shell ldconfig -p | grep $(@F) | sed 's|.*=> ||g') $(R_PREFIX)/lib64/R/lib 56 | 57 | # Build the tarball of R and all of its dependencies 58 | r-%.tar.gz: /tmp/r/% lib/libgfortran.so.3 lib/libquadmath.so.0 lib/libgomp.so.1 pkgs prune 59 | tar zcf $@ -C /tmp r/$* 60 | 61 | clean: 62 | @rm -rf $(R_PREFIX) 63 | -------------------------------------------------------------------------------- /test-r.sh: -------------------------------------------------------------------------------- 1 | tar xf r-${R_VERSION}.tar.gz -C /tmp 2 | export PATH=/tmp/r/${R_VERSION}/bin:${PATH} 3 | export LD_LIBRARY_PATH=/tmp/r/${R_VERSION}/lib64/R/lib 4 | Rscript -e "cat('R.home():', R.home(), '\n')" 5 | Rscript -e "sessionInfo()" 6 | if [[ ${INTERACTIVE} == "true" ]]; then 7 | export R_BIOC_VERSION=3.4 8 | mkdir /tmp/test 9 | cd /tmp/test 10 | echo "R_BIOC_VERSION=${R_BIOC_VERSION}" >> .Renviron 11 | echo "options(repos = c(CRAN='https://cloud.r-project.org', BioCsoft='https://bioconductor.org/packages/${R_BIOC_VERSION}/bioc', BioCann='https://bioconductor.org/packages/${R_BIOC_VERSION}/data/annotation', BioCexp='https://bioconductor.org/packages/${R_BIOC_VERSION}/data/experiment', BioCextra='https://bioconductor.org/packages/${R_BIOC_VERSION}/extra'))" >> .Rprofile 12 | bash; 13 | fi 14 | --------------------------------------------------------------------------------