123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- #' Render Dockerized R Markdown Documents
- #'
- #' Render dockerized R Markdown documents using Docker containers.
- #' Rabix tools/workflows will be ran if there is a \code{Rabixfile}
- #' generated by \link{lift} under the same directory.
- #'
- #' Before using \code{drender()}, run \link{lift} on the document
- #' first to generate \code{Dockerfile}.
- #' See \code{vignette('liftr-intro')} for details about the extended
- #' YAML front-matter metadata format and system requirements for
- #' rendering dockerized 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 build_args A character string specifying additional
- #' \code{docker build} arguments. For example,
- #' \code{--pull=true -m="1024m" --memory-swap="-1"}.
- #' @param container_name Docker container name to run.
- #' If not specified, we will generate and use a random name.
- #' @param clean default FALSE, if TRUE, clean all containers before build.
- #' @param reset Should we cleanup the Docker container and
- #' Docker image after getting the rendered result?
- #' @param prebuild a command line string to call before docker build
- #' @param cache default TRUE, if FALSE, build with --no-cache=true
- #' @param rm default FALSE, if TRUE build with --rm
- #' @param port default 80 map to shiny's 3838 service
- #' @param browseURL FALSE, if TRUE open shiny app in the browser.
- #' @param privileged TRUE, with '--privileged=true'
- #' @param dind TRUE run with " -v /var/run/docker.sock:/var/run/docker.sock"
- #' @param ... Additional arguments passed to
- #' \code{\link[rmarkdown]{render}}.
- #'
- #' @return Rendered file is written to the same directory of the input file.
- #' A character vector with the image name and container name will be
- #' returned. You will be able to manage them with \code{docker}
- #' commands later if \code{reset = FALSE}.
- #'
- #' @export drender
- #'
- #' @importFrom rmarkdown render
- #'
- #' @examples
- ## Included in \dontrun{} since users need
- ## Docker and Rabix installed to run them.
- #' # 1. Dockerized R Markdown document
- #' # Docker is required to run the example,
- #' # so make sure we can use `docker` in shell.
- #' dir_docker = paste0(tempdir(), '/drender_docker/')
- #' dir.create(dir_docker)
- #' file.copy(system.file("examples/docker.Rmd", package = "liftr"), dir_docker)
- #' docker_input = paste0(dir_docker, "docker.Rmd")
- #' lift(docker_input)
- #' \dontrun{
- #' drender(docker_input)
- #' # view rendered document
- #' browseURL(paste0(dir_docker, "docker.html"))}
- #'
- #' # 2. Dockerized R Markdown document with Rabix options
- #' # Docker and Rabix are required to run the example,
- #' # so make sure we can use `docker` and `rabix` in shell.
- #' dir_rabix = paste0(tempdir(), '/drender_rabix/')
- #' dir.create(dir_rabix)
- #' file.copy(system.file("template/rabix.Rmd", package = "liftr"), dir_rabix)
- #' rabix_input = paste0(dir_rabix, "rabix.Rmd")
- #' lift(rabix_input)
- #' \dontrun{
- #' drender(rabix_input)
- #' # view rendered document
- #' browseURL(paste0(dir_rabix, "rabix.html"))}
- drender = function (input = NULL,
- tag = NULL, build_args = NULL, container_name = NULL,
- reset = FALSE,
- clean = FALSE,
- cache = TRUE,
- rm = FALSE,
- prebuild = NULL,
- port = 80,
- browseURL = FALSE,
- privileged = TRUE,
- dind = TRUE,
- ...) {
- if (is.null(input))
- stop('missing input file')
- if (!file.exists(normalizePath(input)))
- stop('input file does not exist')
- if(clean){
- message("cleaning containers...")
- system("docker stop $(docker ps -a -q)")
- system("docker rm $(docker ps -a -q)")
- }
- # run rabix first if Rabixfile is found
- rabixfile_path = paste0(file_dir(input), '/Rabixfile')
- if (file.exists(rabixfile_path)) {
- if (Sys.which('rabix') == '')
- stop('Cannot find `rabix` on system search path,
- please ensure we can use `rabix` from shell')
- rabix_cmd = paste0(readLines(rabixfile_path), collapse = '\n')
- system(rabix_cmd)
- }
- # docker build
- if(!is.na(file.info(input)$isdir) && file.info(input)$isdir){
- dockerfile_path = file.path(dirname(input), '/Dockerfile')
- }else{
- dockerfile_path = paste0(file_dir(input), '/Dockerfile')
- }
- if (!file.exists(dockerfile_path))
- stop('Cannot find Dockerfile in the same directory of input file')
- 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), tolower(file_name_sans(input)), tag)
- docker_build_cmd = paste0("docker build ",
- ifelse(cache, " ", " --no-cache=true "),
- ifelse(rm, " --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 ",
- ifelse(privileged, " -privileged=true"," "),
- ifelse(dind, " -v /var/run/docker.sock:/var/run/docker.sock", " "),
- ifelse(rm, " --rm ", " "),
- "--name \"", container_name,
- "\" -u `id -u $USER` -v \"",
- file_dir(dockerfile_path), ":", "/liftrroot/\" ",
- image_name,
- " /usr/bin/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) {
- if(get_type(input) == "shinydoc"){
- docker_run_cmd = paste0(docker_run_cmd_base,
- render_engine(input),
- "(",quote_str(basename(input)),");",
- "file.copy(", quote_str(c(basename(input),"/srv/shiny-server/")),
- ")\"")
- }else{
- docker_run_cmd = paste0(docker_run_cmd_base, render_engine(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.')
- }
- if(is_shinyapp(input)){
- dots_arg$file = file_name(input)
- }else{
- 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_engine(input), ", ",render_args, ")")
- docker_run_cmd = paste0(docker_run_cmd_base, render_cmd, "\"")
- }
- if(!is.null(prebuild)){
- message(prebuild)
- system2(prebuild)
- }
- message(docker_build_cmd)
- system(docker_build_cmd)
- if(is_shinyapp(input) || is_shinydoc(input)){
- shiny_run = paste("docker run -dp ", paste0(port, ":3838 "), image_name)
- url = paste0("http://localhost:", port, "/", basename(input))
- if(browseURL){
- message("lauching shiny app in docker ...")
- system(shiny_run)
- message("Open browser for ", url)
- browseURL(url)
- }else{
- message("run docker like this: ", shiny_run)
- message("You can view it at ", url)
- }
- }else{
- message(docker_run_cmd)
- system(docker_run_cmd)
- }
- # cleanup docker containers and images
- # TODO: needs exception handling
- if (reset) {
- system(paste0("docker stop \"", container_name, "\""))
- system(paste0("docker rm -f \"", container_name, "\""))
- system(paste0("docker rmi -f \"", image_name, "\""))
- }
- if(is_shinyapp(input)){
- return(list('image_name' = image_name,
- 'container_name' = container_name,
- 'shiny_run' = shiny_run,
- 'url' = url))
- }else{
- return(list('image_name' = image_name, 'container_name' = container_name))
- }
- }
|