123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- #' Render Containerized R Markdown Documents
- #'
- #' @description
- #' Render R Markdown documents using Docker.
- #'
- #' @details
- #' Before using this function, please run \code{\link{lift}} on the
- #' RMD document first to generate the \code{Dockerfile}.
- #'
- #' After a successful rendering, you will be able to clean up the
- #' Docker image with \code{\link{prune_image}}.
- #'
- #' Please see \code{vignette('liftr-intro')} for details of the extended
- #' YAML metadata format and system requirements for writing and rendering
- #' containerized R Markdown documents.
- #'
- #' @param input Input file to render in Docker container.
- #' @param tag Docker image name to build, sent as docker argument \code{-t}.
- #' If not specified, it will use the same name as the input file.
- #' @param container_name Docker container name to run.
- #' If not specified, will use a randomly generated name.
- #' @param cache Logical. Controls the \code{--no-cache} argument
- #' in \code{docker run}. Setting this to be \code{TRUE} can accelerate
- #' the rendering speed substantially for repeated/interactive rendering
- #' since the Docker image layers will be cached, with only the changed
- #' (knitr related) image layer being updated. Default is \code{TRUE}.
- #' @param build_args A character string specifying additional
- #' \code{docker build} arguments. For example,
- #' \code{--pull=true -m="1024m" --memory-swap="-1"}.
- #' @param run_args A character string specifying additional
- #' \code{docker run} arguments. For example, \code{--privileged=true}.
- #' @param prune Logical. Should we clean up all dangling containers,
- #' volumes, networks, and images in case the rendering was not successful?
- #' Default is \code{TRUE}.
- #' @param prune_info Logical. Should we save the Docker container and
- #' image information to a YAML file (name ended with \code{.docker.yml})
- #' for manual pruning or inspections later? Default is \code{TRUE}.
- #' @param ... Additional arguments passed to
- #' \code{\link[rmarkdown]{render}}.
- #'
- #' @return
- #' \itemize{
- #' \item A list containing the image name, container name,
- #' and Docker commands will be returned.
- #' \item An YAML file ending with \code{.docker.yml} storing the
- #' image name, container name, and Docker commands for rendering
- #' this document will be written to the directory of the input file.
- #' \item The rendered output will be written to the directory of the
- #' input file.
- #' }
- #'
- #' @export render_docker
- #'
- #' @importFrom rmarkdown render
- #' @importFrom yaml as.yaml
- #'
- #' @examples
- ## Included in \dontrun{} since users need Docker installed to run them.
- #' # copy example file
- #' dir_example = paste0(tempdir(), "/liftr-tidyverse/")
- #' dir.create(dir_example)
- #' file.copy(system.file("examples/liftr-tidyverse.Rmd", package = "liftr"), dir_example)
- #'
- #' # containerization
- #' input = paste0(dir_example, "liftr-tidyverse.Rmd")
- #' lift(input)
- #'
- #' \dontrun{
- #' # render the document with Docker
- #' render_docker(input)
- #'
- #' # view rendered document
- #' browseURL(paste0(dir_example, "liftr-tidyverse.pdf"))
- #'
- #' # remove the generated Docker image
- #' prune_image(paste0(dir_example, "liftr-tidyverse.docker.yml"))}
- render_docker = function(
- input = NULL, tag = NULL, container_name = NULL,
- cache = TRUE, build_args = NULL, run_args = NULL,
- prune = TRUE, prune_info = TRUE, ...) {
- if (is.null(input))
- stop('missing input file')
- if (!file.exists(normalizePath(input)))
- stop('input file does not exist')
- # docker build
- dockerfile_path = paste0(file_dir(input), '/Dockerfile')
- if (!file.exists(dockerfile_path))
- stop('Cannot find Dockerfile in the same directory of input file,
- please containerize the R Markdown document via lift() first.')
- if (Sys.which('docker') == '')
- stop('Cannot find `docker` on system search path,
- please ensure we can use `docker` from shell')
- image_name = ifelse(is.null(tag), file_name_sans(input), tag)
- cache = paste0("--no-cache=", ifelse(cache, "false", "true"))
- docker_build_cmd = paste0(
- "docker build ", cache, " --rm=true ",
- build_args, " -t=\"", image_name, "\" ",
- file_dir(dockerfile_path))
- # docker run
- container_name = ifelse(
- is.null(container_name),
- paste0('liftr_container_', uuid()),
- container_name)
- docker_run_cmd_base = paste0(
- "docker run --rm ", run_args,
- " --name \"", container_name,
- "\" -u `id -u $USER` -v \"",
- file_dir(dockerfile_path), ":", "/liftrroot/\" ",
- image_name,
- " Rscript -e \"library('knitr');library('rmarkdown');",
- "library('shiny');setwd('/liftrroot/');")
- # process additional arguments passed to rmarkdown::render()
- dots_arg = list(...)
- if (length(dots_arg) == 0L) {
- docker_run_cmd = paste0(
- docker_run_cmd_base, "render(input = '",
- file_name(input), "')\"")
- } else {
- if (!is.null(dots_arg$input))
- stop('input can only be specified once')
- if (!is.null(dots_arg$output_file) |
- !is.null(dots_arg$output_dir) |
- !is.null(dots_arg$intermediates_dir)) {
- stop('`output_file`, `output_dir`, and `intermediates_dir`
- are not supported to be changed now, we will consider
- this in the next versions.')
- }
- dots_arg$input = file_name(input)
- tmp = tempfile()
- dput(dots_arg, file = tmp)
- render_args = paste0(readLines(tmp), collapse = '\n')
- render_cmd = paste0("do.call(render, ", render_args, ')')
- docker_run_cmd = paste0(docker_run_cmd_base, render_cmd, "\"")
- }
- # output container and image info before rendering
- res = list(
- 'container_name' = container_name,
- 'image_name' = image_name,
- 'docker_build_cmd' = docker_build_cmd,
- 'docker_run_cmd' = docker_run_cmd)
- if (prune_info) {
- writeLines(as.yaml(res), con = paste0(
- file_dir(input), '/', file_name_sans(input), '.docker.yml'))
- }
- # render
- system(docker_build_cmd)
- system(docker_run_cmd)
- # cleanup dangling containers, images, volumes, and networks
- if (prune) {
- cat('Cleaning up...\n')
- on.exit(system('docker system prune --force'))
- }
- res
- }
- #' @rdname render_docker
- #' @export drender
- drender = function(...) {
- .Deprecated('render_docker')
- }
|