diff --git a/R/lambda-deploy.R b/R/lambda-deploy.R index c554a5b..bb0a02b 100644 --- a/R/lambda-deploy.R +++ b/R/lambda-deploy.R @@ -3,10 +3,18 @@ #' @param tag A name for the Docker container and Lambda function #' @param runtime_function name of the runtime function #' @param runtime_path path to the script containing the runtime function -#' @param dependencies list of dependencies +#' @param renvlock_path path to the renv.lock file (if any) +#' @param dependencies list of dependencies (if any) +#' +#' @details Use either `renvlock_path` or `dependencies` to install required +#' packages, not both. By default, both are `NULL`, so the Docker image will +#' have no additional packages installed. +#' #' @importFrom glue glue glue_collapse single_quote double_quote #' @export -build_lambda <- function(tag, runtime_function, runtime_path, dependencies) { +build_lambda <- function( + tag, runtime_function, runtime_path, + renvlock_path = NULL, dependencies = NULL) { logger::log_info("[build_lambda] Checking system dependencies.") check_system_dependencies() @@ -22,6 +30,7 @@ build_lambda <- function(tag, runtime_function, runtime_path, dependencies) { folder = folder, runtime_function = runtime_function, runtime_path = runtime_path, + renvlock_path = renvlock_path, dependencies = dependencies ) }, diff --git a/R/lambda-util.R b/R/lambda-util.R index 0e798a0..fc1578b 100644 --- a/R/lambda-util.R +++ b/R/lambda-util.R @@ -71,6 +71,15 @@ runtime_line <- function(runtime) { glue("CMD [{rt}]") } +#' renv_line +#' @noRd +renv_line <- function(renvlock_path) { + checkmate::assert_character(renvlock_path) + cmd1 <- glue("COPY {renvlock_path} renv.lock") + cmd2 <- glue("RUN Rscript -e 'renv::restore()'") + glue_collapse(c(cmd1, cmd2), sep = "\n") +} + #' parse password from ecr token #' @noRd parse_password <- function(ecr_token) { @@ -85,7 +94,12 @@ parse_password <- function(ecr_token) { #' @param folder path to store the Dockerfile #' @param runtime_function name of the runtime function #' @param runtime_path path to the script containing the runtime function -#' @param dependencies list of dependencies +#' @param renvlock_path path to the renv.lock file (if any) +#' @param dependencies list of dependencies (if any) +#' +#' @details Use either `renvlock_path` or `dependencies` to install required +#' packages, not both. By default, both are `NULL`, so the Docker image will +#' have no additional packages installed. #' #' @examples #' \dontrun{ @@ -110,7 +124,8 @@ create_lambda_dockerfile <- function(folder, runtime_function, runtime_path, - dependencies) { + renvlock_path = NULL, + dependencies = NULL) { logger::log_debug("[create_lambda_dockerfile] Validating inputs.") checkmate::assert_character( @@ -155,6 +170,29 @@ create_lambda_dockerfile <- null.ok = TRUE ) + checkmate::assert_character( + x = renvlock_path, + min.chars = 1, + null.ok = TRUE + ) + + if (!is.null(renvlock_path)) { + if (!checkmate::test_file_exists(renvlock_path)) { + msg <- glue( + "[create_lambda_dockerfile] Can't access renv.lock file {renvlock_path}." # nolint + ) + logger::log_error(msg) + rlang::abort(msg) + } + } + + # Can't use both dependencies and renvlock + if (!is.null(renvlock_path) && !is.null(dependencies)) { + msg <- "[create_lambda_dockerfile] Can't use both dependencies and renv." + logger::log_error(msg) + rlang::abort(msg) + } + logger::log_debug( "[create_lambda_dockerfile] Creating directory with Dockerfile and runtime script." # nolint ) @@ -191,6 +229,15 @@ create_lambda_dockerfile <- ) } + if (!is.null(renvlock_path)) { + file.copy(renvlock_path, folder) + renv_string <- renv_line(basename(renvlock_path)) + write(renv_string, + file = file.path(folder, "Dockerfile"), + append = TRUE + ) + } + runtime_string <- runtime_line(runtime_function) write(runtime_string, diff --git a/README.Rmd b/README.Rmd index bf35a50..4eb239e 100644 --- a/README.Rmd +++ b/README.Rmd @@ -62,6 +62,7 @@ on setting credentials, region, profile, etc. ```{r, eval = FALSE} runtime_function <- "parity" runtime_path <- system.file("parity.R", package = "r2lambda") +renvlock_path <- system.file("renv.lock", package = "r2lambda") dependencies <- NULL # Might take a while, its building a docker image @@ -69,6 +70,7 @@ build_lambda( tag = "parity1", runtime_function = runtime_function, runtime_path = runtime_path, + renvlock_path = renvlock_path, dependencies = dependencies ) ``` diff --git a/inst/lambdr_dockerfile.txt b/inst/lambdr_dockerfile.txt index 333c8d8..81bb547 100644 --- a/inst/lambdr_dockerfile.txt +++ b/inst/lambdr_dockerfile.txt @@ -14,13 +14,11 @@ ENV PATH="${PATH}:/opt/R/${R_VERSION}/bin/" # System requirements for R packages RUN yum -y install openssl-devel -RUN Rscript -e "install.packages(c('httr', 'jsonlite', 'logger', 'remotes'), repos = 'https://packagemanager.rstudio.com/all/__linux__/centos7/latest')" -RUN Rscript -e "remotes::install_github('mdneuzerling/lambdr')" - -RUN mkdir /lambda -COPY runtime.R /lambda -RUN chmod 755 -R /lambda +RUN R -e 'install.packages(c("remotes", "lambdr", "renv"), repos = "https://packagemanager.rstudio.com/all/__linux__/centos7/latest")' RUN printf '#!/bin/sh\ncd /lambda\nRscript runtime.R' > /var/runtime/bootstrap \ && chmod +x /var/runtime/bootstrap +WORKDIR /lambda +COPY runtime.R runtime.R +RUN chmod 755 -R /lambda diff --git a/inst/renv.lock b/inst/renv.lock new file mode 100644 index 0000000..855c5e1 --- /dev/null +++ b/inst/renv.lock @@ -0,0 +1,127 @@ +{ + "R": { + "Version": "4.4.1", + "Repositories": [ + { + "Name": "CRAN", + "URL": "https://cloud.r-project.org" + } + ] + }, + "Packages": { + "R6": { + "Package": "R6", + "Version": "2.5.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "470851b6d5d0ac559e9d01bb352b4021" + }, + "askpass": { + "Package": "askpass", + "Version": "1.2.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "sys" + ], + "Hash": "c39f4155b3ceb1a9a2799d700fbd4b6a" + }, + "curl": { + "Package": "curl", + "Version": "5.2.3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "d91263322a58af798f6cf3b13fd56dde" + }, + "httr": { + "Package": "httr", + "Version": "1.4.7", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "R6", + "curl", + "jsonlite", + "mime", + "openssl" + ], + "Hash": "ac107251d9d9fd72f0ca8049988f1d7f" + }, + "jsonlite": { + "Package": "jsonlite", + "Version": "1.8.9", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "methods" + ], + "Hash": "4e993b65c2c3ffbffce7bb3e2c6f832b" + }, + "lambdr": { + "Package": "lambdr", + "Version": "1.2.5", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "httr", + "jsonlite", + "logger" + ], + "Hash": "c5054d8272a53671dd101a17a34419a7" + }, + "logger": { + "Package": "logger", + "Version": "0.3.0", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "utils" + ], + "Hash": "c145edf05cc128e6ffcfa5d872c46329" + }, + "mime": { + "Package": "mime", + "Version": "0.12", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "tools" + ], + "Hash": "18e9c28c1d3ca1560ce30658b22ce104" + }, + "openssl": { + "Package": "openssl", + "Version": "2.2.2", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "askpass" + ], + "Hash": "d413e0fef796c9401a4419485f709ca1" + }, + "renv": { + "Package": "renv", + "Version": "1.0.7", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "utils" + ], + "Hash": "397b7b2a265bc5a7a06852524dabae20" + }, + "sys": { + "Package": "sys", + "Version": "3.4.3", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "de342ebfebdbf40477d0758d05426646" + } + } +} diff --git a/man/build_lambda.Rd b/man/build_lambda.Rd index 543abbe..341ff6b 100644 --- a/man/build_lambda.Rd +++ b/man/build_lambda.Rd @@ -4,7 +4,13 @@ \alias{build_lambda} \title{build and tag lambda image locally} \usage{ -build_lambda(tag, runtime_function, runtime_path, dependencies) +build_lambda( + tag, + runtime_function, + runtime_path, + renvlock_path = NULL, + dependencies = NULL +) } \arguments{ \item{tag}{A name for the Docker container and Lambda function} @@ -13,8 +19,15 @@ build_lambda(tag, runtime_function, runtime_path, dependencies) \item{runtime_path}{path to the script containing the runtime function} -\item{dependencies}{list of dependencies} +\item{renvlock_path}{path to the renv.lock file (if any)} + +\item{dependencies}{list of dependencies (if any)} } \description{ build and tag lambda image locally } +\details{ +Use either `renvlock_path` or `dependencies` to install required +packages, not both. By default, both are `NULL`, so the Docker image will +have no additional packages installed. +} diff --git a/tests/testthat/test-lambda-util.R b/tests/testthat/test-lambda-util.R index 6fff2ca..af890ee 100644 --- a/tests/testthat/test-lambda-util.R +++ b/tests/testthat/test-lambda-util.R @@ -1,4 +1,3 @@ - if (FALSE) { # only in in interactive session test_that("aws_connect fails ok when bad service is requested", { @@ -11,7 +10,6 @@ if (FALSE) { # only in in interactive session } test_that("install_deps_line fails ok with incorrect input", { - deps <- list("a") expect_error(install_deps_line(deps = deps)) @@ -20,11 +18,9 @@ test_that("install_deps_line fails ok with incorrect input", { deps <- c(1) expect_error(install_deps_line(deps = deps)) - }) test_that("runtime_line fails ok with incorrect input", { - runtime <- list("a") expect_error(runtime_line(runtime = runtime)) @@ -33,7 +29,6 @@ test_that("runtime_line fails ok with incorrect input", { runtime <- c(1) expect_error(runtime_line(runtime = runtime)) - }) test_that("parse_password works", { @@ -58,13 +53,25 @@ test_that("runtime_line works with correct input", { expect_equal(test, 'CMD ["my_fun"]') }) +test_that("renv_line works", { + expect_error(renv_line(renvlock = 1)) -##### + renvlock <- renv_line("renv.lock") + test <- glue::glue_collapse( + c( + "COPY renv.lock renv.lock", + "RUN Rscript -e 'renv::restore()'" + ), + sep = "\n" + ) + expect_equal(test, renvlock) +}) -folder <- tempdir() -unlink(folder, recursive = TRUE) +##### test_that("create_lambda_dockerfile works with correct input", { + folder <- file.path(tempdir(), "test1") + unlink(folder, recursive = TRUE) runtime_function <- "parity" runtime_path <- system.file("parity.R", package = "r2lambda") @@ -75,7 +82,7 @@ test_that("create_lambda_dockerfile works with correct input", { runtime_function = runtime_function, runtime_path = runtime_path, dependencies = dependencies - ) + ) expect_true(dir.exists(folder)) expect_equal(dir(folder), c("Dockerfile", "runtime.R")) @@ -83,6 +90,8 @@ test_that("create_lambda_dockerfile works with correct input", { }) test_that("create_lambda_dockerfile fails as expected", { + folder <- file.path(tempdir(), "test2") + unlink(folder, recursive = TRUE) runtime_function <- "party" runtime_path <- system.file("party.R", package = "r2lambda") @@ -90,11 +99,12 @@ test_that("create_lambda_dockerfile fails as expected", { expect_error( create_lambda_dockerfile( - folder = folder, - runtime_function = runtime_function, - runtime_path = runtime_path, - dependencies = dependencies - )) + folder = folder, + runtime_function = runtime_function, + runtime_path = runtime_path, + dependencies = dependencies + ) + ) unlink(folder, recursive = TRUE) runtime_function <- list("party") @@ -107,7 +117,8 @@ test_that("create_lambda_dockerfile fails as expected", { runtime_function = runtime_function, runtime_path = runtime_path, dependencies = dependencies - )) + ) + ) unlink(folder, recursive = TRUE) runtime_function <- NA @@ -151,65 +162,68 @@ test_that("create_lambda_dockerfile fails as expected", { ) ) unlink(folder, recursive = TRUE) - }) ##### -folder <- tempdir() -unlink(folder, recursive = TRUE) - test_that("create_lambda_image fails ok when inputs are incorrect", { + folder <- file.path(tempdir(), paste0("test", runif(1))) + unlink(folder, recursive = TRUE, force = TRUE) + + tag <- "testtag" + folder <- file.path(tempdir(), paste0("test", runif(1))) + expect_error(create_lambda_image(folder = folder, tag = tag)) + + tag <- "testtag" + folder <- file.path(tempdir(), paste0("test", runif(1))) + dir.create(folder) + expect_error(create_lambda_image(folder = folder, tag = tag)) + unlink(folder, recursive = TRUE) - tag <- "testtag" - folder <- tempdir() - expect_error(create_lambda_image(folder = folder, tag = tag)) - - tag <- "testtag" - folder <- tempdir() - dir.create(folder) - expect_error(create_lambda_image(folder = folder, tag = tag)) - unlink(folder, recursive = TRUE) - - tag <- "testtag" - folder <- tempdir() - dir.create(folder) - file.create(file.path(folder, "Dockerfile")) - expect_error(create_lambda_image(folder = folder, tag = tag)) - unlink(folder, recursive = TRUE) - - tag <- "testtag" - folder <- tempdir() - dir.create(folder) - file.create(file.path(folder, "runtime.R")) - expect_error(create_lambda_image(folder = folder, tag = tag)) - unlink(folder, recursive = TRUE) - - tag <- "testtag" - folder <- tempdir() - dir.create(folder) - file.create(file.path(folder, "Dockerfile")) - file.create(file.path(folder, "runtime.R")) - expect_error(create_lambda_image(folder = folder, tag = tag)) - unlink(folder, recursive = TRUE) - - tag <- "testtag" - folder <- tempdir() - dir.create(folder) - file.create(file.path(folder, "Dockerfile")) - write(x = "test that file is not empty", file.path(folder, "Dockerfile"), append = TRUE) - file.create(file.path(folder, "runtime.R")) - expect_error(create_lambda_image(folder = folder, tag = tag)) - unlink(folder, recursive = TRUE) - - tag <- "testtag" - folder <- tempdir() - dir.create(folder) - file.create(file.path(folder, "Dockerfile")) - file.create(file.path(folder, "runtime.R")) - write(x = "test that file is not empty", file.path(folder, "runtime.R"), append = TRUE) - expect_error(create_lambda_image(folder = folder, tag = tag)) - unlink(folder, recursive = TRUE) + tag <- "testtag" + folder <- file.path(tempdir(), paste0("test", runif(1))) + dir.create(folder) + file.create(file.path(folder, "Dockerfile")) + expect_error(create_lambda_image(folder = folder, tag = tag)) + unlink(folder, recursive = TRUE) + + tag <- "testtag" + folder <- file.path(tempdir(), paste0("test", runif(1))) + dir.create(folder) + file.create(file.path(folder, "runtime.R")) + expect_error(create_lambda_image(folder = folder, tag = tag)) + unlink(folder, recursive = TRUE) + + tag <- "testtag" + folder <- file.path(tempdir(), paste0("test", runif(1))) + dir.create(folder) + file.create(file.path(folder, "Dockerfile")) + file.create(file.path(folder, "runtime.R")) + expect_error(create_lambda_image(folder = folder, tag = tag)) + unlink(folder, recursive = TRUE) + tag <- "testtag" + folder <- file.path(tempdir(), paste0("test", runif(1))) + dir.create(folder) + file.create(file.path(folder, "Dockerfile")) + write( + x = "test that file is not empty", + file.path(folder, "Dockerfile"), append = TRUE + ) + file.create(file.path(folder, "runtime.R")) + expect_error(create_lambda_image(folder = folder, tag = tag)) + unlink(folder, recursive = TRUE) + + tag <- "testtag" + folder <- file.path(tempdir(), paste0("test", runif(1))) + dir.create(folder) + file.create(file.path(folder, "Dockerfile")) + file.create(file.path(folder, "runtime.R")) + write( + x = "test that file is not empty", + file.path(folder, "runtime.R"), append = TRUE + ) + expect_error(create_lambda_image(folder = folder, tag = tag)) + unlink(folder, recursive = TRUE) })