# Use Cases

## Overview

The `pracpac` package enables developers to easily incorporate R packages into Docker images. What follows is a reproducible demonstration of select `pracpac` use cases. Note that this vignette is by no means exhaustive, and the pattern of delivering an R package within a Docker image1 may prove useful in other scenarios as well.

## Pipeline

The “pipeline” use case describes a scenario where a developer may want to use functions from a custom R package to perform processing on the input and/or output of a domain-specific tool. There are countless software packages that will not be implemented directly in R. Developers who want to leverage these tools in a reproducible context may choose to do so by using Docker. If the tool(s) in the workflow require(s) upstream or downstream processing that is best suited to R code, then the developer could write an R package and distribute everything together in Docker. Besides defining reproducible dependencies, Docker allows the developer to pass “instructions” for how the container should behave. These can include scripts to run when the container is launched.

To demonstrate this use case we use the `hellow` R package source code that ships with `pracpac`. We write a pipeline to use the `isay()` function from `hellow` to randomly select a flavor of “Hello”. The output is piped to a command-line tool called `translate-shell`2 that translates the text to another language specified by the user (by default French). When the container runs, the pipeline is executed and outputs the translated results.

### Setting up the Docker template

We will first move the example `hellow` package to a temporary location:

    library(pracpac)
    library(fs)

    ## specify the temp directory
    tmp <- tempdir()
    ## copy the example hellow package to the temp directory
    dir_copy(path = system.file("hellow", package = "pracpac"), new_path = path(tmp, "example", "hellow"))

The new directory includes the R package source contents of `hellow`:

    dir_tree(path(tmp, "example", "hellow"), recurse = TRUE)

    ├── DESCRIPTION
    ├── NAMESPACE
    ├── R
    │   └── hello.R
    └── man
        └── isay.Rd

We can use `use_docker(..., use_case="pipeline")` to create the template of files for building the Docker image:

    use_docker(pkg_path = path(tmp, "example", "hellow"), use_case = "pipeline")

    Using renv. Dockerfile will build from renv.lock in /tmp/RtmpsMexB6/example/hellow/docker.
    Using template for the specified use case: pipeline
    Writing dockerfile: /tmp/RtmpsMexB6/example/hellow/docker/Dockerfile
    The directory will be created at /tmp/RtmpsMexB6/example/hellow/docker/assets
    Assets for the specified use case (pipeline) will be copied there.
    The specified use case (pipeline) includes the following asset: run.sh
    The specified use case (pipeline) includes the following asset: pre.R
    The specified use case (pipeline) includes the following asset: post.R
    Building package hellow version 0.1.0 in /tmp/RtmpsMexB6/example/hellow/hellow_0.1.0.tar.gz
    docker build command:
    docker build  --tag hellow:latest --tag hellow:0.1.0 /tmp/RtmpsMexB6/example/hellow/docker

The directory now contains the `docker/` subdirectory, which has another subdirectory called `assets/` for the templated pipeline scripts:

    dir_tree(path(tmp, "example", "hellow"), recurse = TRUE)

    ├── DESCRIPTION
    ├── NAMESPACE
    ├── R
    │   └── hello.R
    ├── docker
    │   ├── Dockerfile
    │   ├── assets
    │   │   ├── post.R
    │   │   ├── pre.R
    │   │   └── run.sh
    │   ├── hellow_0.1.0.tar.gz
    │   └── renv.lock
    └── man
        └── isay.Rd

The files need to be edited as follows:

#### `Dockerfile`

    FROM rocker/r-ver:latest

    ## copy the renv.lock into the image
    COPY renv.lock /renv.lock

    ## install renv
    RUN Rscript -e 'install.packages(c("renv"))'

    ## set the renv path var to the renv lib
    ENV RENV_PATHS_LIBRARY renv/library

    ## restore packages from renv.lock
    RUN Rscript -e 'renv::restore(lockfile = "/renv.lock", repos = NULL)'

    ## copy in built R package
    COPY hellow_0.1.0.tar.gz /hellow_0.1.0.tar.gz

    ## run script to install built R package from source
    RUN Rscript -e 'install.packages("/hellow_0.1.0.tar.gz", type="source", repos=NULL)'

    ## COPY in the pre processing R script and run shell script
    COPY assets/pre.R /pre.R
    COPY assets/run.sh /run.sh

    ## add the translate-shell tool and dependencies
    RUN apt-get update && apt-get install -y bsdmainutils translate-shell

    ## enter at run shell script
    ## allows for parameters passed to container at runtime
    ENTRYPOINT ["bash", "/run.sh"]

#### `run.sh`

    #!/bin/bash

    ## get tranlsation language from first argument
    ## default to :fr
    TOLANG="${1:-:fr}"
    HELLO=$(Rscript /pre.R)

    trans "$HELLO" "$TOLANG"

#### `pre.R`

    library(hellow)

    isay()

Note that in this case the `docker/assets/post.R` template is not necessary, so we can delete it:

    file_delete(path(tmp, "example", "hellow", "docker", "assets", "post.R"))

### Building the image

With the template files created and edited as described above, we can build the image:

    build_image(pkg_path = path(tmp, "example", "hellow"))

In this case, the image will be built with `build_image()` default behavior of tagging with the package name and version:

    system("docker images")

    hellow                            0.1.0        e1a9bc2ebbb5   15 seconds ago   959MB
    hellow                            latest       e1a9bc2ebbb5   15 seconds ago   959MB

### Running the container

Now we can run the container, either from a Docker client directly on the host or from within R:

    system("docker run --rm hellow:latest")

    You are peachy!
    [1] "Hello"

    [1] "Bonjour"

    Translations of [1] "Hello"
    [ English -> Français ]

    [1] "Hello"
        [1] "Bonjour", [1] "Salut"

    system("docker run --rm hellow:latest :es")

    You are groundbreaking!
    [1] "What's up?"

    [1] "¿Qué pasa?"

    Translations of [1] "What's up?"
    [ English -> Español ]

    [1] "What's up?"
        [1] "¿Qué pasa?", [1] "¿Qué hay de nuevo?"

## Shiny

The “shiny” use case enables quick and intuitive templating of tools necessary to host a Shiny app in a Docker container. There are a number of ways that developers could ship a Shiny app inside an R package, and the example that follows demonstrates one strategy for doing so.

To demonstrate this use case we use the `ocf` R package source code that ships with `pracpac`. The `ocf` package wraps a custom Shiny app that we’ve titled “Old Colorful”. This app is intended to provide basic motivation for how a Shiny image could be templated, built, and used with `pracpac`. In short, the app code uses the bones of the “Old Faithful” Shiny app and simply adds a feature to add color to the eruptions histogram. The color palette is selected at random from palettes in the `wesanderson` package via a function called `get_pal()`. This function is embedded in a reactive context such that when the user clicks a button, the histogram plot title and bar colors dynamically update.

### Setting up the Docker template

We will first move the example `ocf` package to a temporary location:

    library(pracpac)
    library(fs)

    ## specify the temp directory
    tmp <- tempdir()
    ## copy the example ocf package to the temp directory
    dir_copy(path = system.file("ocf", package = "pracpac"), new_path = path(tmp, "example", "ocf"))

    dir_tree(path(tmp, "example", "ocf"), recurse = TRUE)

    ├── DESCRIPTION
    ├── NAMESPACE
    ├── R
    │   └── pal.R
    ├── inst
    │   └── app
    │       └── app.R
    └── man
        └── get_pal.Rd

We can use `use_docker(..., use_case="shiny")` to create the template of files for building the Docker image:

    use_docker(pkg_path = path(tmp, "example", "ocf"), use_case = "shiny")

    Using renv. Dockerfile will build from renv.lock in /tmp/RtmpsMexB6/example/ocf/docker.
    Using template for the specified use case: shiny
    Writing dockerfile: /tmp/RtmpsMexB6/example/ocf/docker/Dockerfile
    The directory will be created at /tmp/RtmpsMexB6/example/ocf/docker/assets
    Assets for the specified use case (shiny) will be copied there.
    The specified use case (shiny) includes the following asset: app.R
    Building package ocf version 0.1.0 in /tmp/RtmpsMexB6/example/ocf/ocf_0.1.0.tar.gz
    docker build command:
    docker build  --tag ocf:latest --tag ocf:0.1.0 /tmp/RtmpsMexB6/example/ocf/docker

The directory now contains the `docker/` subdirectory, which has another subdirectory called `assets/` for the templated Shiny files:

    dir_tree(path(tmp, "example", "ocf"), recurse = TRUE)

    ├── DESCRIPTION
    ├── NAMESPACE
    ├── R
    │   └── pal.R
    ├── docker
    │   ├── Dockerfile
    │   ├── assets
    │   │   └── app.R
    │   ├── ocf_0.1.0.tar.gz
    │   └── renv.lock
    ├── inst
    │   └── app
    │       └── app.R
    └── man
        └── get_pal.Rd

One of the files needs to be edited as follows:

#### `app.R`

    library(shiny)
    library(ocf)

    # Define UI for application that draws a histogram
    ui <- fluidPage(

      # Application title
      titlePanel("Old Faithful Geyser Data (In Color!)"),

      # Sidebar with a slider input for number of bins
      sidebarLayout(
        sidebarPanel(
          sliderInput("bins",
                      "Number of bins:",
                      min = 1,
                      max = 50,
                      value = 30),
          actionButton(inputId = "color", label = "Add Color")
        ),

        # Show a plot of the generated distribution
        mainPanel(
          plotOutput("distPlot")
        )
      )
    )

    # Define server logic required to draw a histogram
    server <- function(input, output) {

      color_palette <- eventReactive(input$color, {
        get_pal()
      })

      output$distPlot <- renderPlot({
        # generate bins based on input$bins from ui.R
        x    <- faithful[, 2]
        bins <- seq(min(x), max(x), length.out = input$bins + 1)

        # draw the histogram with the specified number of bins
        hist(x,
             breaks = bins,
             col = color_palette()[[1]][1],
             border = color_palette()[[1]][5],
             main = paste0("Histogram in ", names(color_palette())))
      })
    }

    # Run the application
    shinyApp(ui = ui, server = server)

Note that for this particular example, the templated Dockerfile requires no editing. In some cases, it may be necessary to edit the Dockerfile to modify behavior of the Shiny server (e.g., edit the shiny-server.conf file).

### Building the image

With the template files created and edited as described above, we can build the image:

    build_image(pkg_path = path(tmp, "example", "ocf"))

In this case, the image will be built with `build_image()` default behavior of tagging with the package name and version:

    system("docker images")

### Running the container

Now we can run the container, either from a Docker client directly on the host or from within R:

    system("docker run --rm -it -d --user shiny -p 3838:3838 ocf:0.1.0")

In this case we run the container detached (`-d`) and use the port 3838 such we can navigate to `localhost:3838` in a web browser and view the running app.

1. Note that we discuss Docker terminology in the “Basic Usage” vignette: `vignette("basic-usage", package = "pracpac")`↩︎

2. https://github.com/soimort/translate-shell↩︎