├── .Rbuildignore ├── .github ├── .gitignore └── workflows │ ├── R-CMD-check.yaml │ └── pkgdown.yaml ├── .gitignore ├── CITATION.cff ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── build.R ├── docker.R ├── pracpac-package.R ├── sysdata.rda ├── utils-pipe.R ├── utils.R └── zzz.R ├── README.Rmd ├── README.md ├── _pkgdown.yml ├── cran-comments.md ├── data-raw └── sysdata.R ├── inst ├── example │ ├── hellow │ │ ├── Dockerfile │ │ ├── pre.R │ │ └── run.sh │ └── ocf │ │ └── app.R ├── hellow │ ├── DESCRIPTION │ ├── NAMESPACE │ ├── R │ │ └── hello.R │ └── man │ │ └── isay.Rd ├── ocf │ ├── DESCRIPTION │ ├── NAMESPACE │ ├── R │ │ └── pal.R │ ├── inst │ │ └── app │ │ │ └── app.R │ └── man │ │ └── get_pal.Rd └── templates │ ├── base │ ├── base-renv.dockerfile │ └── base.dockerfile │ └── use_cases │ ├── README.md │ ├── pipeline │ ├── assets │ │ ├── post.R │ │ ├── pre.R │ │ └── run.sh │ └── pipeline.dockerfile │ ├── rstudio │ └── rstudio.dockerfile │ ├── shiny │ ├── assets │ │ └── app.R │ └── shiny.dockerfile │ └── use_cases.csv ├── man ├── add_assets.Rd ├── add_dockerfile.Rd ├── build_image.Rd ├── build_pkg.Rd ├── create_docker_dir.Rd ├── figures │ ├── logo-large.png │ ├── logo.png │ ├── pracpac-workflow.drawio │ └── pracpac-workflow.png ├── handle_use_case.Rd ├── pipe.Rd ├── pkg_info.Rd ├── pkg_root.Rd ├── pracpac-package.Rd ├── renv_deps.Rd └── use_docker.Rd ├── pracpac.Rproj ├── tests ├── testthat.R └── testthat │ └── test_files.R └── vignettes ├── .gitignore ├── basic-usage.Rmd └── use-cases.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^pracpac\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^LICENSE\.md$ 4 | ^README\.Rmd$ 5 | pracpac_*.tar.gz 6 | pracpac.tar.gz 7 | ^CITATION\.cff$ 8 | ^docker$ 9 | ^data-raw$ 10 | ^doc$ 11 | ^Meta$ 12 | ^_pkgdown\.yml$ 13 | ^docs$ 14 | ^pkgdown$ 15 | ^\.github$ 16 | ^cran-comments\.md$ 17 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: R-CMD-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macos-latest, r: 'release'} 22 | - {os: windows-latest, r: 'release'} 23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 24 | - {os: ubuntu-latest, r: 'release'} 25 | - {os: ubuntu-latest, r: 'oldrel-1'} 26 | 27 | env: 28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 29 | R_KEEP_PKG_SOURCE: yes 30 | 31 | steps: 32 | - uses: actions/checkout@v3 33 | 34 | - uses: r-lib/actions/setup-pandoc@v2 35 | 36 | - uses: r-lib/actions/setup-r@v2 37 | with: 38 | r-version: ${{ matrix.config.r }} 39 | http-user-agent: ${{ matrix.config.http-user-agent }} 40 | use-public-rspm: true 41 | 42 | - uses: r-lib/actions/setup-r-dependencies@v2 43 | with: 44 | extra-packages: any::rcmdcheck 45 | needs: check 46 | 47 | - uses: r-lib/actions/check-r-package@v2 48 | with: 49 | upload-snapshots: true 50 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | release: 9 | types: [published] 10 | workflow_dispatch: 11 | 12 | name: pkgdown 13 | 14 | jobs: 15 | pkgdown: 16 | runs-on: ubuntu-latest 17 | # Only restrict concurrency for non-PR jobs 18 | concurrency: 19 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 20 | env: 21 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 22 | steps: 23 | - uses: actions/checkout@v3 24 | 25 | - uses: r-lib/actions/setup-pandoc@v2 26 | 27 | - uses: r-lib/actions/setup-r@v2 28 | with: 29 | use-public-rspm: true 30 | 31 | - uses: r-lib/actions/setup-r-dependencies@v2 32 | with: 33 | extra-packages: any::pkgdown, local::. 34 | needs: website 35 | 36 | - name: Build site 37 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 38 | shell: Rscript {0} 39 | 40 | - name: Deploy to GitHub pages 🚀 41 | if: github.event_name != 'pull_request' 42 | uses: JamesIves/github-pages-deploy-action@v4.4.1 43 | with: 44 | clean: false 45 | branch: gh-pages 46 | folder: docs 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | pracpac_*.tar.gz 6 | pracpac.tar.gz 7 | docker/ 8 | inst/doc 9 | /doc/ 10 | /Meta/ 11 | docs 12 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------- 2 | # CITATION file created with {cffr} R package, v0.4.1 3 | # See also: https://docs.ropensci.org/cffr/ 4 | # ----------------------------------------------------------- 5 | 6 | cff-version: 1.2.0 7 | message: 'To cite package "pracpac" in publications use:' 8 | type: software 9 | license: MIT 10 | title: 'pracpac: Practical R Packaging in Docker' 11 | version: 0.1.0 12 | abstract: Streamline the creation of Docker images with R packages and dependencies 13 | embedded. The pracpac package provides a usethis-like interface to creating Dockerfiles 14 | with dependencies managed by renv. 15 | authors: 16 | - family-names: Turner 17 | given-names: Stephen 18 | orcid: https://orcid.org/0000-0001-9140-9028 19 | - family-names: Nagraj 20 | given-names: VP 21 | email: nagraj@nagraj.net 22 | orcid: https://orcid.org/0000-0003-0060-566X 23 | url: https://signaturescience.github.io/pracpac/ 24 | contact: 25 | - family-names: Nagraj 26 | given-names: VP 27 | email: nagraj@nagraj.net 28 | orcid: https://orcid.org/0000-0003-0060-566X 29 | references: 30 | - type: software 31 | title: magrittr 32 | abstract: 'magrittr: A Forward-Pipe Operator for R' 33 | notes: Imports 34 | url: https://magrittr.tidyverse.org 35 | repository: https://CRAN.R-project.org/package=magrittr 36 | authors: 37 | - family-names: Bache 38 | given-names: Stefan Milton 39 | email: stefan@stefanbache.dk 40 | - family-names: Wickham 41 | given-names: Hadley 42 | email: hadley@rstudio.com 43 | year: '2023' 44 | - type: software 45 | title: glue 46 | abstract: 'glue: Interpreted String Literals' 47 | notes: Imports 48 | url: https://glue.tidyverse.org/ 49 | repository: https://CRAN.R-project.org/package=glue 50 | authors: 51 | - family-names: Hester 52 | given-names: Jim 53 | orcid: https://orcid.org/0000-0002-2739-7082 54 | - family-names: Bryan 55 | given-names: Jennifer 56 | email: jenny@rstudio.com 57 | orcid: https://orcid.org/0000-0002-6983-2759 58 | year: '2023' 59 | - type: software 60 | title: fs 61 | abstract: 'fs: Cross-Platform File System Operations Based on ''libuv''' 62 | notes: Imports 63 | url: https://fs.r-lib.org 64 | repository: https://CRAN.R-project.org/package=fs 65 | authors: 66 | - family-names: Hester 67 | given-names: Jim 68 | - family-names: Wickham 69 | given-names: Hadley 70 | email: hadley@rstudio.com 71 | - family-names: Csárdi 72 | given-names: Gábor 73 | email: csardi.gabor@gmail.com 74 | year: '2023' 75 | - type: software 76 | title: rprojroot 77 | abstract: 'rprojroot: Finding Files in Project Subdirectories' 78 | notes: Imports 79 | url: https://rprojroot.r-lib.org/ 80 | repository: https://CRAN.R-project.org/package=rprojroot 81 | authors: 82 | - family-names: Müller 83 | given-names: Kirill 84 | email: krlmlr+r@mailbox.org 85 | orcid: https://orcid.org/0000-0002-1416-3412 86 | year: '2023' 87 | - type: software 88 | title: renv 89 | abstract: 'renv: Project Environments' 90 | notes: Imports 91 | url: https://rstudio.github.io/renv/ 92 | repository: https://CRAN.R-project.org/package=renv 93 | authors: 94 | - family-names: Ushey 95 | given-names: Kevin 96 | email: kevin@rstudio.com 97 | year: '2023' 98 | - type: software 99 | title: pkgbuild 100 | abstract: 'pkgbuild: Find Tools Needed to Build R Packages' 101 | notes: Imports 102 | url: https://github.com/r-lib/pkgbuild 103 | repository: https://CRAN.R-project.org/package=pkgbuild 104 | authors: 105 | - family-names: Wickham 106 | given-names: Hadley 107 | - family-names: Hester 108 | given-names: Jim 109 | - family-names: Csárdi 110 | given-names: Gábor 111 | email: csardi.gabor@gmail.com 112 | year: '2023' 113 | - type: software 114 | title: 'R: A Language and Environment for Statistical Computing' 115 | notes: Depends 116 | url: https://www.R-project.org/ 117 | authors: 118 | - name: R Core Team 119 | location: 120 | name: Vienna, Austria 121 | year: '2023' 122 | institution: 123 | name: R Foundation for Statistical Computing 124 | version: '>= 2.10' 125 | - type: software 126 | title: rmarkdown 127 | abstract: 'rmarkdown: Dynamic Documents for R' 128 | notes: Suggests 129 | url: https://pkgs.rstudio.com/rmarkdown/ 130 | repository: https://CRAN.R-project.org/package=rmarkdown 131 | authors: 132 | - family-names: Allaire 133 | given-names: JJ 134 | email: jj@rstudio.com 135 | - family-names: Xie 136 | given-names: Yihui 137 | email: xie@yihui.name 138 | orcid: https://orcid.org/0000-0003-0645-5666 139 | - family-names: McPherson 140 | given-names: Jonathan 141 | email: jonathan@rstudio.com 142 | - family-names: Luraschi 143 | given-names: Javier 144 | email: javier@rstudio.com 145 | - family-names: Ushey 146 | given-names: Kevin 147 | email: kevin@rstudio.com 148 | - family-names: Atkins 149 | given-names: Aron 150 | email: aron@rstudio.com 151 | - family-names: Wickham 152 | given-names: Hadley 153 | email: hadley@rstudio.com 154 | - family-names: Cheng 155 | given-names: Joe 156 | email: joe@rstudio.com 157 | - family-names: Chang 158 | given-names: Winston 159 | email: winston@rstudio.com 160 | - family-names: Iannone 161 | given-names: Richard 162 | email: rich@rstudio.com 163 | orcid: https://orcid.org/0000-0003-3925-190X 164 | year: '2023' 165 | - type: software 166 | title: knitr 167 | abstract: 'knitr: A General-Purpose Package for Dynamic Report Generation in R' 168 | notes: Suggests 169 | url: https://yihui.org/knitr/ 170 | repository: https://CRAN.R-project.org/package=knitr 171 | authors: 172 | - family-names: Xie 173 | given-names: Yihui 174 | email: xie@yihui.name 175 | orcid: https://orcid.org/0000-0003-0645-5666 176 | year: '2023' 177 | - type: software 178 | title: testthat 179 | abstract: 'testthat: Unit Testing for R' 180 | notes: Suggests 181 | url: https://testthat.r-lib.org 182 | repository: https://CRAN.R-project.org/package=testthat 183 | authors: 184 | - family-names: Wickham 185 | given-names: Hadley 186 | email: hadley@rstudio.com 187 | year: '2023' 188 | version: '>= 3.0.0' 189 | - type: software 190 | title: withr 191 | abstract: 'withr: Run Code ''With'' Temporarily Modified Global State' 192 | notes: Suggests 193 | url: https://withr.r-lib.org 194 | repository: https://CRAN.R-project.org/package=withr 195 | authors: 196 | - family-names: Hester 197 | given-names: Jim 198 | - family-names: Henry 199 | given-names: Lionel 200 | email: lionel@rstudio.com 201 | - family-names: Müller 202 | given-names: Kirill 203 | email: krlmlr+r@mailbox.org 204 | - family-names: Ushey 205 | given-names: Kevin 206 | email: kevinushey@gmail.com 207 | - family-names: Wickham 208 | given-names: Hadley 209 | email: hadley@rstudio.com 210 | - family-names: Chang 211 | given-names: Winston 212 | year: '2023' 213 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: pracpac 2 | Title: Practical 'R' Packaging in 'Docker' 3 | Version: 0.2.0 4 | Authors@R: 5 | c(person(given = "Stephen", 6 | family = "Turner", 7 | role = c("aut"), 8 | comment = c(ORCID = "0000-0001-9140-9028")), 9 | person(given = "VP", 10 | family = "Nagraj", 11 | role = c("cre", "aut"), 12 | email="nagraj@nagraj.net", 13 | comment = c(ORCID = "0000-0003-0060-566X")), 14 | person("Signature Science, LLC.", 15 | role = "cph")) 16 | Description: Streamline the creation of 'Docker' images with 'R' packages and dependencies embedded. The 'pracpac' package provides a 'usethis'-like interface to creating Dockerfiles with dependencies managed by 'renv'. The 'pracpac' functionality is described in Nagraj and Turner (2023) . 17 | License: MIT + file LICENSE 18 | Encoding: UTF-8 19 | Roxygen: list(markdown = TRUE) 20 | RoxygenNote: 7.2.3 21 | Imports: 22 | magrittr, 23 | glue, 24 | fs, 25 | rprojroot, 26 | renv, 27 | pkgbuild 28 | Depends: 29 | R (>= 2.10) 30 | Suggests: 31 | rmarkdown, 32 | knitr, 33 | testthat (>= 3.0.0), 34 | withr 35 | VignetteBuilder: knitr 36 | Config/testthat/edition: 3 37 | URL: https://signaturescience.github.io/pracpac/, https://github.com/signaturescience/pracpac/ 38 | BugReports: https://github.com/signaturescience/pracpac/issues 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2023 2 | COPYRIGHT HOLDER: Signature Science LLC 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2023 Signature Science LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export("%>%") 4 | export(add_assets) 5 | export(add_dockerfile) 6 | export(build_image) 7 | export(build_pkg) 8 | export(create_docker_dir) 9 | export(pkg_info) 10 | export(renv_deps) 11 | export(use_docker) 12 | importFrom(magrittr,"%>%") 13 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # pracpac 0.2.0 2 | 3 | ## New Features 4 | 5 | ### Shiny use case documentation 6 | 7 | As of this release, `pracpac` now includes documentation for the "shiny" use case. The package now features a vignette dedicated to the templating a Docker image to host a Shiny web app. To motivate the example usage, there is now also another example package in the `pracpac` source at `inst/ocf`. 8 | 9 | ## Bug Fixes 10 | 11 | ### `renv_deps()` and strictly versioned package depedencies 12 | 13 | Previously the `renv_deps()` function was not able to parse dependencies in the `DESCRIPTION` file that stated strict versions of packages. The function can now handle this case, such that all dependencies in `DESCRIPTION` (whether they include a strict version number or not) will be passed into the `renv` snapshot procedure. 14 | 15 | # pracpac 0.1.0 16 | 17 | Initial release! 18 | 19 | For more information see or `browseVignettes(package = "pracpac")`. 20 | -------------------------------------------------------------------------------- /R/build.R: -------------------------------------------------------------------------------- 1 | #' Build a package tar.gz 2 | #' 3 | #' @description 4 | #' Builds a package source tar.gz using [pkgbuild::build] and moves it into a user-specified location (default `docker/`). 5 | #' 6 | #' @param pkg_path Path to the package directory. Default is `"."` for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed. 7 | #' @param img_path Path to the write the docker image definition contents. The default `NULL` will use `docker/` as a subdirectory of the `pkg_path`. 8 | #' @param ... Additional optional arguments passed to [pkgbuild::build]. 9 | #' 10 | #' @return Invisibly returns a list of package info returned by [pkg_info], tar.gz source and destination file paths. 11 | #' @export 12 | #' 13 | #' @examples 14 | #' \dontrun{ 15 | #' # Specify path to example package source and copy to tempdir() 16 | #' # Note that in practice you do not need to copy to a tempdir() 17 | #' # And in fact it may be easiest to use pracpac relative to your package directory root 18 | #' ex_pkg_src <- system.file("hellow", package = "pracpac", mustWork = TRUE) 19 | #' file.copy(from = ex_pkg_src, to = tempdir(), recursive = TRUE) 20 | #' 21 | #' # Build the example package from tempdir() 22 | #' build_pkg(pkg = file.path(tempdir(), "hellow")) 23 | #' } 24 | build_pkg <- function(pkg_path=".", img_path = NULL, ...) { 25 | 26 | ## if the image path is not given then construct path as subdirectory of pkg 27 | ## otherwise use the specified image path 28 | if(is.null(img_path)) { 29 | # Construct path to the docker directory 30 | docker_dir <- fs::path(pkg_path, "docker") 31 | } else { 32 | docker_dir <- fs::path(img_path) 33 | } 34 | 35 | # Create the target directory if it doesn't already exist 36 | if(!fs::dir_exists(docker_dir)) { 37 | create_docker_dir(pkg_path = pkg_path, img_path = img_path) 38 | } 39 | 40 | # Get package information and construct filepaths to file built by R CMD build and eventual package tar.gz 41 | info <- pkg_info(pkg_path) 42 | tarsrc <- fs::path(pkg_path, glue::glue("{info$pkgname}_{info$pkgver}.tar.gz")) 43 | 44 | ## Build the package with pkgbuild::build 45 | message(glue::glue("Building package {info$pkgname} version {info$pkgver} in {tarsrc}")) 46 | pkgbuild::build(path = info$pkgroot, dest_path = docker_dir, quiet=TRUE, ...) 47 | 48 | # Return info 49 | return(invisible(list(info=info, tarsrc=tarsrc))) 50 | 51 | } 52 | 53 | #' Build a Docker image 54 | #' 55 | #' @description 56 | #' Builds a Docker image created by [use_docker] or [add_dockerfile]. This function is run as part of [use_docker] when `build = TRUE` is set, but can be used on its own. 57 | #' 58 | #' @param pkg_path Path to the package directory. Default is `"."` for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed. 59 | #' @param img_path Path to the write the docker image definition contents. The default `NULL` will use `docker/` as a subdirectory of the `pkg_path`. 60 | #' @param cache Logical; should caching be used? Default `TRUE`. Set to `FALSE` to use `--no-cache` in `docker build`. 61 | #' @param tag Image tag to use; default is `NULL` and the image will be tagged with package name version from [pkg_info]. 62 | #' @param build Logical as to whether or not the image should be built. Default is `TRUE`, and if `FALSE` the `docker build` command will be messaged. Setting `build=FALSE` could be useful if additional `docker build` options or different tags are desired. In either case the `docker build` command will be returned invisibly. 63 | #' 64 | #' @return Invisibly returns the `docker build` command. Primarily called for its side effects, which runs the `docker build` as a system command. 65 | #' 66 | #' @export 67 | #' 68 | #' @examples 69 | #' \dontrun{ 70 | #' # Specify path to example package source and copy to tempdir() 71 | #' # Note that in practice you do not need to copy to a tempdir() 72 | #' # And in fact it may be easiest to use pracpac relative to your package directory root 73 | #' ex_pkg_src <- system.file("hellow", package = "pracpac", mustWork = TRUE) 74 | #' file.copy(from = ex_pkg_src, to = tempdir(), recursive = TRUE) 75 | #' 76 | #' # Run use_docker to create Docker directory and assets for the example package 77 | #' use_docker(pkg_path = file.path(tempdir(), "hellow")) 78 | #' 79 | #' # Build the image 80 | #' build_image(pkg_path = file.path(tempdir(), "hellow")) 81 | #' # Or construct the image build command without building 82 | #' build_cmd <- build_image(pkg_path = file.path(tempdir(), "hellow"), build=FALSE) 83 | #' build_cmd 84 | #' } 85 | build_image <- function(pkg_path=".", img_path=NULL, cache=TRUE, tag=NULL, build=TRUE) { 86 | 87 | ## if the image path is not given then construct path as subdirectory of pkg 88 | ## otherwise use the specified image path 89 | if(is.null(img_path)) { 90 | # Construct path to the docker directory 91 | docker_dir <- fs::path(pkg_path, "docker") 92 | } else { 93 | docker_dir <- fs::path(img_path) 94 | } 95 | 96 | # Check that a dockerfile exists 97 | dockerfilepath <- fs::path(docker_dir, "Dockerfile") 98 | if (!fs::file_exists(dockerfilepath)) stop(glue::glue("Dockerfile doesn't exist: {dockerfilepath}")) 99 | 100 | # Get package info 101 | info <- pkg_info(pkg_path) 102 | 103 | # Parse docker build options 104 | cache <- ifelse(cache, "", "--no-cache") 105 | 106 | # Construct tags and build command 107 | if(is.null(tag)) { 108 | image_tag1 <- paste0(info$pkgname, ":latest") 109 | image_tag2 <- paste0(info$pkgname, ":", info$pkgver) 110 | buildcmd <- glue::glue("docker build {cache} --tag {image_tag1} --tag {image_tag2} {docker_dir}") 111 | } else { 112 | image_tag <- tag 113 | buildcmd <- glue::glue("docker build {cache} --tag {image_tag} {docker_dir}") 114 | } 115 | 116 | # Message build command and run as a system command if not using a dry run 117 | message("docker build command:") 118 | message(buildcmd) 119 | if (build) { 120 | message("Building docker image...") 121 | system(buildcmd, ignore.stdout=TRUE) 122 | } 123 | 124 | # Return the build command as a character string (this is messaged) 125 | return(invisible(buildcmd)) 126 | 127 | } 128 | -------------------------------------------------------------------------------- /R/docker.R: -------------------------------------------------------------------------------- 1 | #' Create Docker directory 2 | #' 3 | #' @description 4 | #' Creates a `docker/` directory for a given package. By default, assumes that `docker/` should be a subdirectory of the specified package path. 5 | #' 6 | #' @details 7 | #' This function is run as part of [use_docker] but can be used on its own. 8 | #' 9 | #' @param pkg_path Path to the package directory. Default is `"."` for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed. 10 | #' @param img_path Path to the write the docker image definition contents. The default `NULL` will use `docker/` as a subdirectory of the `pkg_path`. 11 | #' @return Invisibly returns a list of package info returned by [pkg_info]. Primarily called for side-effect to create docker directory. 12 | #' 13 | #' @export 14 | #' 15 | #' @examples 16 | #' \dontrun{ 17 | #' # Specify path to example package source and copy to tempdir() 18 | #' # Note that in practice you do not need to copy to a tempdir() 19 | #' # And in fact it may be easiest to use pracpac relative to your package directory root 20 | #' ex_pkg_src <- system.file("hellow", package = "pracpac", mustWork = TRUE) 21 | #' file.copy(from = ex_pkg_src, to = tempdir(), recursive = TRUE) 22 | #' 23 | #' # Assuming default behavior then docker/ will be created under source root 24 | #' create_docker_dir(pkg_path = file.path(tempdir(), "hellow")) 25 | #' 26 | #' # Alternatively you can specify another directory above, below, or beside package source 27 | #' create_docker_dir(pkg_path = file.path(tempdir(), "hellow"), img_path = file.path(tempdir(), "img")) 28 | #' } 29 | #' 30 | create_docker_dir <- function(pkg_path = ".", img_path = NULL) { 31 | 32 | # Check that the path is a package 33 | info <- pkg_info(pkg_path) 34 | 35 | ## if the image path is not given then construct path as subdirectory of pkg 36 | ## otherwise use the specified image path 37 | if(is.null(img_path)) { 38 | # Construct path to the docker directory 39 | docker_dir <- fs::path(pkg_path, "docker") 40 | } else { 41 | docker_dir <- fs::path(img_path) 42 | } 43 | 44 | ## if the directory already exists message that 45 | if (fs::dir_exists(docker_dir)) { 46 | message(glue::glue("Directory already exists: {docker_dir}")) 47 | } else { 48 | message(glue::glue("Creating docker directory: {docker_dir}")) 49 | fs::dir_create(docker_dir) 50 | } 51 | 52 | # If .Rbuildignore doesn't exist, perhaps we should create one 53 | ignore_fp <- fs::path(pkg_path, ".Rbuildignore") 54 | if(!file.exists(ignore_fp)) { 55 | fs::file_create(ignore_fp) 56 | message(glue::glue("Created {ignore_fp} in package {pkg_path}.")) 57 | } 58 | # Only append ^docker$ to .Rbuildignore if ^docker$ isn't already there 59 | if (!any(grepl("\\^docker\\$", readLines(ignore_fp)))) { 60 | message(glue::glue("Adding ^docker$ to {ignore_fp}")) 61 | write("^docker$", file = ignore_fp, append=TRUE) 62 | } 63 | 64 | # Invisibly return package information 65 | return(invisible(info)) 66 | } 67 | 68 | #' Add a Dockerfile to the docker directory 69 | #' 70 | #' @description 71 | #' Adds a Dockerfile to the docker directory created by [create_docker_dir]. 72 | #' Allows for specification of several preset use cases, whether or not use use 73 | #' renv to manage dependencies, and optional overriding the base image. 74 | #' 75 | #' @details 76 | #' This function is run as part of [use_docker] but can be used on its own. 77 | #' 78 | #' See `vignette("use-cases", package="pracpac")` for details on use cases. 79 | #' 80 | #' @param pkg_path Path to the package directory. Default is `"."` for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed. 81 | #' @param img_path Path to the write the docker image definition contents. The default `NULL` will use `docker/` as a subdirectory of the `pkg_path`. 82 | #' @param use_renv Logical; use renv? Defaults to `TRUE`. If `FALSE`, package dependencies are scraped from the `DESCRIPTION` file and the most recent versions will be installed in the image. 83 | #' @param use_case Name of the use case. Defaults to `"default"`, which only uses the base boilerplate. See `vignette("use-cases", package="pracpac")` for other use cases (e.g., `shiny`, `rstudio`, `pipeline`). 84 | #' @param base_image Name of the base image to start `FROM`. Default is `NULL` and the base image will be derived based on `use_case.` Optionally override this by setting the name of the base image (including tag if desired). 85 | #' @param repos Option to override the repos used for installing packages with `renv` by passing name of repository. Only used if `use_renv = TRUE`. Default is `NULL` meaning that the repos specified in `renv` lockfile will remain as-is and not be overridden. 86 | #' 87 | #' @return Invisibly returns a list of package info returned by [pkg_info]. Primarily called for side-effect to create Dockerfile. 88 | #' 89 | #' @export 90 | #' 91 | #' @examples 92 | #' \dontrun{ 93 | #' 94 | #' # Specify path to example package source and copy to tempdir() 95 | #' # Note that in practice you do not need to copy to a tempdir() 96 | #' # And in fact it may be easiest to use pracpac relative to your package directory root 97 | #' ex_pkg_src <- system.file("hellow", package = "pracpac", mustWork = TRUE) 98 | #' file.copy(from = ex_pkg_src, to = tempdir(), recursive = TRUE) 99 | #' 100 | #' # Default: FROM rocker/r-ver:latest with no additional template 101 | #' # By default add_dockerfile requires you either to specify use_renv = FALSE 102 | #' # Or run renv_deps() prior to add_dockerfile() 103 | #' # The use_docker() wrapper runs these sequentially, and is recommended for most usage 104 | #' add_dockerfile(pkg_path = file.path(tempdir(), "hellow"), use_renv = FALSE) 105 | #' # Specify tidyverse base image 106 | #' renv_deps(pkg_path = file.path(tempdir(), "hellow")) 107 | #' add_dockerfile(pkg_path = file.path(tempdir(), "hellow"), base_image="rocker/tidyverse:4.2.2") 108 | #' # Specify different default repo 109 | #' add_dockerfile(pkg_path = file.path(tempdir(), "hellow"), repos="https://cran.wustl.edu/") 110 | #' # RStudio template 111 | #' add_dockerfile(pkg_path = file.path(tempdir(), "hellow"), use_case="rstudio") 112 | #' # Shiny template 113 | #' add_dockerfile(pkg_path = file.path(tempdir(), "hellow"), use_case = "shiny") 114 | #' # Pipeline template 115 | #' add_dockerfile(pkg_path = file.path(tempdir(), "hellow"), use_case="pipeline") 116 | #' } 117 | add_dockerfile <- function(pkg_path = ".", img_path = NULL, use_renv = TRUE, use_case="default", base_image = NULL, repos=NULL) { 118 | 119 | # handle the use_case argument 120 | ## this first check if the use case is valid 121 | ## then will pull out relevant specs (stored as a named list) that we can use later to construct dockerfile 122 | use_case_specs <- handle_use_case(use_case) 123 | 124 | # Get canonical path 125 | pkg_path <- fs::path_real(pkg_path) 126 | 127 | # Check that path is a package 128 | info <- pkg_info(pkg_path) 129 | 130 | # Turn the string vector: c("a", "b", "c") to the single element string "'a','b','c'". 131 | # If info$pkgdeps is empty, create a string 'character(0)' that gets glued into the dockerfile template. 132 | # Note this must be a quoted string 'character(0)', not an empty string character(0), for the glue to work properly. 133 | if (length(info$pkgdeps==0L)) { 134 | pkgs <- paste(paste0('"', info$pkgdeps,'"'), collapse=',') 135 | } else { 136 | pkgs <- 'character(0)' 137 | } 138 | 139 | ## if the image path is not given then construct path as subdirectory of pkg 140 | ## otherwise use the specified image path 141 | if(is.null(img_path)) { 142 | # Construct path to the docker directory 143 | docker_dir <- fs::path(pkg_path, "docker") 144 | } else { 145 | docker_dir <- fs::path(img_path) 146 | } 147 | 148 | ## if the docker_dir specified above doesnt exist ... create it with the helper 149 | if(!fs::dir_exists(docker_dir)) { 150 | create_docker_dir(pkg_path = pkg_path, img_path = img_path) 151 | } 152 | 153 | ## create the dockerfile 154 | dockerfile_fp <- fs::path(docker_dir, "Dockerfile") 155 | invisible(fs::file_create(dockerfile_fp)) 156 | 157 | # Choose a different base template depending on whether you're using renv 158 | if(use_renv) { 159 | if (!fs::file_exists(fs::path(docker_dir, "renv.lock"))) { 160 | stop(glue::glue("use_renv=TRUE but no renv.lock file found in {docker_dir}. Run renv_deps() to generate.")) 161 | } 162 | message(glue::glue("Using renv. Dockerfile will build from renv.lock in {docker_dir}.")) 163 | base_template_fp <- system.file("templates/base/base-renv.dockerfile", package = "pracpac", mustWork = TRUE) 164 | } else { 165 | message(glue::glue("Not using renv. Pulling package dependencies from description file: c({pkgs})")) 166 | base_template_fp <- system.file("templates/base/base.dockerfile", package = "pracpac", mustWork = TRUE) 167 | } 168 | 169 | ## handle base image ... 170 | ## using either the "base_image" argument (if not NULL) ... 171 | ## or the base_image from use_case_specs 172 | if(is.null(base_image)) { 173 | base_image <- use_case_specs$base_image 174 | } 175 | 176 | # Read in the base template and create the dockerfile base using glue to pull in base image, other pkgs, pkg name and version 177 | base_template <- paste0(readLines(base_template_fp), collapse = "\n") 178 | ## NOTE: this step is important ... 179 | ## the NULL option should NOT be quoted when glued into template ... 180 | ## but the repo names should be quoted .. 181 | ## so we need to add a double layer of quotes in this statement if you specify repos 182 | repos <- ifelse(is.null(repos), 'NULL', paste0('"', repos, '"')) 183 | base_dockerfile <- glue::glue(base_template, base_image = base_image, pkgs = pkgs, pkgname=info$pkgname, pkgver=info$pkgver, repos = repos) 184 | 185 | ## check the use case in use_case_specs (see above for parsing with handle_use_case) 186 | ## if it is default then just use the base dockerfile 187 | ## if not then find the template and append that to the base dockerfile 188 | if (use_case_specs$use_case == "default") { 189 | dockerfile_contents <- base_dockerfile 190 | } else { 191 | # Read in the use case template 192 | message(glue::glue("Using template for the specified use case: {use_case_specs$use_case}")) 193 | use_case_template_fp <- system.file(use_case_specs$template, package = "pracpac", mustWork = TRUE) 194 | use_case_dockerfile <- paste0(readLines(use_case_template_fp), collapse = "\n") 195 | # Stitch the base dockerfile and use_case dockerfile together 196 | dockerfile_contents <- glue::glue(paste(base_dockerfile, use_case_dockerfile, sep="\n\n")) 197 | } 198 | 199 | # Write dockerfile to disk 200 | message(glue::glue("Writing dockerfile: {dockerfile_fp}")) 201 | write(dockerfile_contents, file = dockerfile_fp, append = FALSE) 202 | 203 | # Invisibly return pkg info 204 | return(invisible(info)) 205 | } 206 | 207 | #' Get dependencies using renv 208 | #' 209 | #' @description 210 | #' Get dependencies using renv. This function will inspect your package specified 211 | #' at `pkg_path` (default is current working directory, `.`), and create an renv lock file (`renv.lock`) in 212 | #' the `docker/` directory. More information about the `renv` implementation is provided in the Details section. 213 | # 214 | #' @details 215 | #' The `renv.lock` file will capture all your package's dependencies (and all 216 | #' their dependencies) at the current version installed on your system at the 217 | #' time this function is run. When using the default `use_renv=TRUE` in 218 | #' [use_docker] or [add_dockerfile], the resulting `Dockerfile` will install 219 | #' packages from this `renv.lock` file using [renv::restore]. This ensures that 220 | #' versions of dependencies in the image mirror what is installed on your system 221 | #' at the time of image creation, rather than potentially newer versions on package repositories like 222 | #' CRAN or Bioconductor, which may come with breaking changes that you are unaware of at the 223 | #' time of package development. 224 | #' 225 | #' If there are additional R packages that may be useful for the Docker image you plan to build (but may not be captured under your package dependencies), then you can add these packages to the `renv` procedure with the "other_packages" argument. 226 | #' 227 | #' This function is run as part of [use_docker] but can be used on its own. 228 | #' 229 | #' @param pkg_path Path to the package directory. Default is `"."` for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed. 230 | #' @param img_path Path to the write the docker image definition contents. The default `NULL` will use `docker/` as a subdirectory of the `pkg_path`. 231 | #' @param other_packages Vector of other packages to be included in `renv` lock file; default is `NULL`. 232 | #' @param overwrite Logical; should an existing lock file should be overwritten? Default is `TRUE`. 233 | #' @param consent_renv Logical; give renv consent in this session with `options(renv.consent = TRUE)`? Default is `TRUE`. See [renv::consent] for details. 234 | #' 235 | #' @return Invisibly returns a list of package info returned by [pkg_info]. Primarily called for side effect. Writes an `renv` lock file to the docker/ directory. 236 | #' 237 | #' @export 238 | #' 239 | #' @examples 240 | #' \dontrun{ 241 | #' # Specify path to example package source and copy to tempdir() 242 | #' # Note that in practice you do not need to copy to a tempdir() 243 | #' # And in fact it may be easiest to use pracpac relative to your package directory root 244 | #' ex_pkg_src <- system.file("hellow", package = "pracpac", mustWork = TRUE) 245 | #' file.copy(from = ex_pkg_src, to = tempdir(), recursive = TRUE) 246 | #' 247 | #' # Run using defaults; only gets current package dependencies 248 | #' renv_deps(pkg_path = file.path(tempdir(), "hellow")) 249 | #' # Add additional packages not explicitly required by your package 250 | #' renv_deps(pkg_path = file.path(tempdir(), "hellow"), other_packages=c("shiny", "knitr")) 251 | #' } 252 | renv_deps <- function(pkg_path = ".", img_path = NULL, other_packages = NULL, overwrite = TRUE, consent_renv=TRUE) { 253 | 254 | # Consent renv 255 | if (consent_renv) { 256 | options(renv.consent = TRUE) 257 | } 258 | 259 | # Get canonical path 260 | pkg_path <- fs::path_real(pkg_path) 261 | 262 | # Check that path is a package 263 | info <- pkg_info(pkg_path) 264 | 265 | ## get pkgname from pkg_info helper 266 | pkgname <- info$pkgname 267 | 268 | ## if the image path is not given then construct path as subdirectory of pkg 269 | ## otherwise use the specified image path 270 | if(is.null(img_path)) { 271 | # Construct path to the docker directory 272 | docker_dir <- fs::path(pkg_path, "docker") 273 | } else { 274 | docker_dir <- fs::path(img_path) 275 | } 276 | 277 | ## if the docker_dir specified above doesnt exist ... create it with the helper 278 | if(!fs::dir_exists(docker_dir)) { 279 | create_docker_dir(pkg_path = pkg_path, img_path = img_path) 280 | } 281 | 282 | ## establish out path for the renv lock file 283 | out_path <- fs::path(docker_dir, "renv.lock") 284 | 285 | ## check if existing lockfile should be retained 286 | if(fs::file_exists(out_path)) { 287 | if(!overwrite) { 288 | message(glue::glue("Overwrite option is set to FALSE and lock file exists: {out_path}")) 289 | return(invisible(info)) 290 | } else { 291 | message(glue::glue("Overwrite option is set to TRUE and existing lock file will be written: {out_path}")) 292 | } 293 | } 294 | ## NOTE: need to pass a tempdir in otherwise renv can't find pkgname when run in current directory ... 295 | ## ... not sure exactly why that is but this seems to work 296 | message(glue::glue("Creating renv lock file with renv::snapshot: {out_path}")) 297 | if (!is.null(other_packages)) message(glue::glue("With additional packages: {paste(other_packages, collapse=', ')}")) 298 | renv::snapshot(project = tempdir(), packages = c(info$pkgdeps, other_packages), lockfile = out_path, prompt = FALSE, update = TRUE) 299 | 300 | # Invisibly return package information 301 | return(invisible(info)) 302 | 303 | } 304 | 305 | 306 | #' Add assets for the specified use case 307 | #' 308 | #' @description 309 | #' Add template assets for the use case specified in [add_dockerfile] or [use_docker]. 310 | #' 311 | #' @details 312 | #' Example #1: the `"shiny"` use case requires than an `app.R` file moved into 313 | #' `/srv/shiny-server/` in the container image. Using `add_assets(use_case="shiny")` 314 | #' (or when using the `"shiny"` use case in [add_dockerfile] or [use_docker]) 315 | #' will create a placeholder `assets/app.R` in the `docker/` directory. The 316 | #' Dockerfile for the `"shiny"` use case will place `COPY assets/app.R/srv/shiny-server` into the Dockerfile. 317 | #' 318 | #' Example #2: the `"pipeline"` use case creates boilerplate for moving pre- and 319 | #' post-processing R and shell scripts into the container at 320 | #' `add_assets(use_case="pipeline")` (or when using the `"pipeline"` use case in 321 | #' [add_dockerfile] or [use_docker]) will create a placeholder `assets/pre.R`, 322 | #' `assets/post.R`, and `assets/run.sh` into the `docker/assets` directory. The 323 | #' Dockerfile for the `"pipeline"` use case will place `COPY assets/run.sh /run.sh` into the Dockerfile. 324 | #' 325 | #' This function is run as part of [use_docker] but can be used on its own. 326 | #' 327 | #' See `vignette("use-cases", package="pracpac")` for details on use cases. 328 | #' 329 | #' @param pkg_path Path to the package directory. Default is `"."` for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed. 330 | #' @param img_path Path to the write the docker image definition contents. The default `NULL` will use `docker/` as a subdirectory of the `pkg_path`. 331 | #' @param use_case Name of the use case. Defaults to `"default"`, which only uses the base boilerplate. 332 | #' @param overwrite Logical; should existing assets should be overwritten? Default is `TRUE`. 333 | #' 334 | #' @return Invisibly returns assets per [handle_use_case]. Called primarily for its side effects. 335 | #' 336 | #' @export 337 | #' 338 | #' @examples 339 | #' \dontrun{ 340 | #' 341 | #' # Specify path to example package source and copy to tempdir() 342 | #' # Note that in practice you do not need to copy to a tempdir() 343 | #' # And in fact it may be easiest to use pracpac relative to your package directory root 344 | #' ex_pkg_src <- system.file("hellow", package = "pracpac", mustWork = TRUE) 345 | #' file.copy(from = ex_pkg_src, to = tempdir(), recursive = TRUE) 346 | #' 347 | #' # Add assets for shiny use case 348 | #' add_assets(pkg_path = file.path(tempdir(), "hellow"), use_case="shiny") 349 | #' # Add assets for pipeline use case 350 | #' add_assets(pkg_path = file.path(tempdir(), "hellow"), use_case="pipeline") 351 | #' } 352 | add_assets <- function(pkg_path = ".", img_path = NULL, use_case = "default", overwrite = TRUE) { 353 | 354 | # handle the use_case argument 355 | ## this first check if the use case is valid 356 | ## then will pull out relevant specs (stored as a named list) that we can use later to construct dockerfile 357 | use_case_specs <- handle_use_case(use_case) 358 | 359 | # Get canonical path 360 | pkg_path <- fs::path_real(pkg_path) 361 | 362 | # Check that path is a package 363 | info <- pkg_info(pkg_path) 364 | 365 | ## if the image path is not given then construct path as subdirectory of pkg 366 | ## otherwise use the specified image path 367 | if(is.null(img_path)) { 368 | # Construct path to the docker directory 369 | docker_dir <- fs::path(pkg_path, "docker") 370 | } else { 371 | docker_dir <- fs::path(img_path) 372 | } 373 | 374 | ## if the docker_dir specified above doesnt exist ... create it with the helper 375 | if(!fs::dir_exists(docker_dir)) { 376 | create_docker_dir(pkg_path = pkg_path, img_path = img_path) 377 | } 378 | 379 | ## if there are no assets then output a message saying so 380 | if(is.na(use_case_specs$assets)) { 381 | message(glue::glue("No assets to add for the specfied use case: {use_case_specs$use_case}")) 382 | } else { 383 | ## otherwise split assets string (separated by ";" if there is more than one) 384 | assets <- strsplit(use_case_specs$assets, split = ";")[[1]] 385 | 386 | assets_dir <- fs::path(docker_dir, "assets") 387 | 388 | ## create the assets subdirectory in docker dir if it is not already there 389 | if(!fs::dir_exists(assets_dir)) { 390 | message(glue::glue("The directory will be created at {assets_dir} \nAssets for the specified use case ({use_case_specs$use_case}) will be copied there.")) 391 | fs::dir_create(assets_dir) 392 | } else { 393 | message(glue::glue("The assets directory already exists at {assets_dir} \nAssets for the specified use case ({use_case_specs$use_case}) will be copied there.")) 394 | } 395 | 396 | ## copy each asset to a subdirectory of docker dir called assets 397 | for(i in 1:length(assets)) { 398 | ## get path to the asset 399 | tmp_asset <- assets[i] 400 | ## get basename to make it easier to construct destination path 401 | tmp_asset_bn <- basename(tmp_asset) 402 | message(glue::glue("The specified use case ({use_case_specs$use_case}) includes the following asset: {tmp_asset_bn}")) 403 | ## copy the asset from the installed pracpac package files to the destination dir 404 | fs::file_copy(system.file(tmp_asset, package = "pracpac", mustWork = TRUE), fs::path(docker_dir, "assets", tmp_asset_bn), overwrite = overwrite) 405 | ## insert pkg name in R files 406 | if(grepl("\\.[rR]$", fs::path(docker_dir, "assets", tmp_asset_bn))) { 407 | tmp_r <- paste0(readLines(fs::path(docker_dir, "assets", tmp_asset_bn)), collapse="\n") 408 | write(paste0("library(", info$pkgname, ")\n", tmp_r), file = fs::path(docker_dir, "assets", tmp_asset_bn), append = FALSE) 409 | } 410 | } 411 | 412 | } 413 | 414 | # Invisibly return package information 415 | return(invisible(use_case_specs$assets)) 416 | 417 | } 418 | 419 | #' Use docker packaging tools 420 | #' 421 | #' @description 422 | #' Wrapper function around other `pracpac` functions. See help for the functions linked below for detail on individual functions. 423 | #' All arguments to `use_docker()` are passed to downstream functions. `use_docker()` will sequentially run: 424 | #' 1. [pkg_info] to get information about the current R package. 425 | #' 1. [create_docker_dir] to create the `docker/` directory in the specified location, if it doesn't already exist. 426 | #' 1. [renv_deps] (if `use_renv=TRUE`, the default) to capture package dependencies with renv and create an `renv.lock` file 427 | #' 1. [add_dockerfile] to create a Dockerfile using template specified by `use_case` 428 | #' 1. [add_assets] depending on the `use_case` 429 | #' 1. [build_pkg] to build the current R package source .tar.gz, and place it into the `docker/` directory 430 | #' 1. [build_image] optional, default `FALSE`; if TRUE, will build the Docker image. 431 | #' 432 | #' The default `build=FALSE` means that everything up to `build_image()` is run, 433 | #' but the image is not actually built. Instead, `use_docker()` will message the 434 | #' `docker build` command, and return that string in `$buildcmd` in the 435 | #' invisibly returned output. 436 | #' 437 | #' See `vignette("use-cases", package="pracpac")` for details on use cases. 438 | #' 439 | #' @param pkg_path Path to the package directory. Default is `"."` for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed. 440 | #' @param img_path Path to the write the docker image definition contents. The default `NULL` will use `docker/` as a subdirectory of the `pkg_path`. 441 | #' @param use_renv Logical; use renv? Defaults to `TRUE`. If `FALSE`, package dependencies are scraped from the `DESCRIPTION` file without version information. 442 | #' @param use_case Name of the use case. Defaults to `"default"`, which only uses the base boilerplate. 443 | #' @param base_image Name of the base image to start `FROM`. Default is `NULL` and the base image will be derived based on `use_case`. Optionally override this by setting the name of the base image (including tag if desired). 444 | #' @param other_packages Vector of other packages to be included in `renv` lock file; default is `NULL`. 445 | #' @param build Logical as to whether or not the image should be built. Default is `TRUE`, and if `FALSE` the `docker build` command will be messaged. Setting `build=FALSE` could be useful if additional `docker build` options or different tags are desired. In either case the `docker build` command will be returned invisibly. 446 | #' @param repos Option to override the repos used for installing packages with `renv` by passing name of repository. Only used if `use_renv = TRUE`. Default is `NULL` meaning that the repos specified in `renv` lockfile will remain as-is and not be overridden. 447 | #' @param overwrite_assets Logical; should existing asset files should be overwritten? Default is `TRUE`. 448 | #' @param overwrite_renv Logical; should an existing lock file should be overwritten? Default is `TRUE`; ignored if `use_renv = TRUE`. 449 | #' @param consent_renv Logical; give renv consent in this session with `options(renv.consent = TRUE)`? Default is `TRUE`. See [renv::consent] for details. 450 | #' 451 | #' @return Invisibly returns a list with information about the package (`$info`) and 452 | #' the `docker build` command (`$buildcmd`). Primarily called for side effect. 453 | #' Creates `docker/` directory, identifies renv dependencies and creates lock 454 | #' file (if `use_renv = TRUE`), writes Dockerfile, builds package tar.gz, 455 | #' moves all relevant assets to the `docker/` directory, and builds Docker 456 | #' image (if `build = TRUE`). 457 | #' 458 | #' @export 459 | #' 460 | #' @examples 461 | #' \dontrun{ 462 | #' 463 | #' # Specify path to example package source and copy to tempdir() 464 | #' # Note that in practice you do not need to copy to a tempdir() 465 | #' # And in fact it may be easiest to use pracpac relative to your package directory root 466 | #' ex_pkg_src <- system.file("hellow", package = "pracpac", mustWork = TRUE) 467 | #' file.copy(from = ex_pkg_src, to = tempdir(), recursive = TRUE) 468 | #' 469 | #' # Run use_docker to create Docker directory and assets for the example package 470 | #' use_docker(pkg_path = file.path(tempdir(), "hellow")) 471 | #' # To not use renv 472 | #' use_docker(pkg_path = file.path(tempdir(), "hellow"), use_renv=FALSE) 473 | #' # To specify a use case 474 | #' use_docker(pkg_path = file.path(tempdir(), "hellow"), use_case="pipeline") 475 | #' # To overwrite the default base image 476 | #' use_docker(pkg_path = file.path(tempdir(), "hellow"), base_image="alpine:latest") 477 | #' } 478 | use_docker <- function(pkg_path = ".", 479 | img_path = NULL, 480 | use_renv = TRUE, 481 | use_case = "default", 482 | base_image = NULL, 483 | other_packages = NULL, 484 | build = FALSE, 485 | repos = NULL, 486 | overwrite_assets = TRUE, 487 | overwrite_renv = TRUE, 488 | consent_renv = TRUE) { 489 | 490 | ## check the package path 491 | info <- pkg_info(pkg_path) 492 | 493 | ## if the image path is not given then construct path as subdirectory of pkg 494 | ## otherwise use the specified image path 495 | if(is.null(img_path)) { 496 | # Construct path to the docker directory 497 | docker_dir <- fs::path(pkg_path, "docker") 498 | } else { 499 | docker_dir <- fs::path(img_path) 500 | } 501 | 502 | ## if the docker_dir specified above doesnt exist ... create it with the helper 503 | if(!fs::dir_exists(docker_dir)) { 504 | create_docker_dir(pkg_path = pkg_path, img_path = img_path) 505 | } 506 | 507 | ## if using renv then make sure the renv_deps runs and outputs lockfile in docker/ dir 508 | if(use_renv) { 509 | renv_deps(pkg_path = pkg_path, img_path = img_path, other_packages = other_packages, overwrite = overwrite_renv, consent_renv=consent_renv) 510 | } 511 | 512 | ## add the dockerfile to the docker/ dir 513 | add_dockerfile(pkg_path = pkg_path, 514 | img_path = img_path, 515 | use_renv = use_renv, 516 | base_image = base_image, 517 | use_case = use_case, 518 | repos = repos) 519 | 520 | ## add the assets 521 | add_assets(pkg_path = pkg_path, 522 | img_path = img_path, 523 | use_case = use_case, 524 | overwrite = overwrite_assets) 525 | 526 | ## build the package tar.gz and copy that to the docker dir/ 527 | build_pkg(pkg_path = pkg_path, img_path = img_path) 528 | 529 | ## conditionally build the image 530 | buildcmd <- build_image(pkg_path = pkg_path, img_path = img_path, build = build) 531 | 532 | # Invisibly return package info 533 | return(invisible(list(info=info, buildcmd=buildcmd))) 534 | 535 | } 536 | -------------------------------------------------------------------------------- /R/pracpac-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | "_PACKAGE" 3 | 4 | ## usethis namespace: start 5 | ## usethis namespace: end 6 | NULL 7 | -------------------------------------------------------------------------------- /R/sysdata.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signaturescience/pracpac/5e426dce72ebfd129dec0ed2b0b7eefc273576a7/R/sysdata.rda -------------------------------------------------------------------------------- /R/utils-pipe.R: -------------------------------------------------------------------------------- 1 | #' Pipe operator 2 | #' 3 | #' See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details. 4 | #' 5 | #' @name %>% 6 | #' @rdname pipe 7 | #' @keywords internal 8 | #' @export 9 | #' @importFrom magrittr %>% 10 | #' @usage lhs \%>\% rhs 11 | #' @param lhs A value or the magrittr placeholder. 12 | #' @param rhs A function call using the magrittr semantics. 13 | #' @return The result of calling `rhs(lhs)`. 14 | NULL 15 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | #' Get information about the current package 2 | #' 3 | #' @description 4 | #' Returns information about the current package in a list which can be passed to other functions. 5 | #' 6 | #' @param pkg_path Path to the package directory. Default is `"."` for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed. 7 | #' @param ... Arguments passed to [rprojroot::find_package_root_file]. 8 | #' @return A list of information about the package. 9 | #' - `pkgroot`: Root directory of the package. 10 | #' - `pkgdeps`: Package dependencies from `Imports` in the `DESCRIPTION`. 11 | #' - `descfile`: File path to the `DESCRIPTION` file. 12 | #' - `pkgname`: Package name. 13 | #' - `pkgver`: Package version. 14 | #' @export 15 | #' @examples 16 | #' \dontrun{ 17 | #' # Specify path to example package source and copy to tempdir() 18 | #' # Note that in practice you do not need to copy to a tempdir() 19 | #' # And in fact it may be easiest to use pracpac relative to your package directory root 20 | #' ex_pkg_src <- system.file("hellow", package = "pracpac", mustWork = TRUE) 21 | #' file.copy(from = ex_pkg_src, to = tempdir(), recursive = TRUE) 22 | #' 23 | #' # This will succeed if this is a package 24 | #' pkg_info(pkg_path = file.path(tempdir(), "hellow")) 25 | #' # This will fail if this is not a package location 26 | #' pkg_info(pkg_path = tempdir()) 27 | #' } 28 | pkg_info <- function(pkg_path=".", ...) { 29 | # Find the package root 30 | pkg_root <- pkg_root(pkg_path=pkg_path, ...) 31 | 32 | # Find the description file 33 | descfile <- fs::path(pkg_root, "DESCRIPTION") 34 | 35 | # Get package dependencies. 36 | # If there are no dependencies, make pkgdeps an empty string, character(0). 37 | # Both of these are perfectly valid, and result in nothing actually being installed: 38 | # BiocManager::install(c(character(0)), update=FALSE, ask=FALSE) 39 | # install.packages(c(character(0))) 40 | imports <- as.data.frame(read.dcf(descfile),stringsAsFactors=FALSE)$Imports 41 | if (is.null(imports)) { 42 | pkgdeps <- character(0) 43 | } else { 44 | imports <- strsplit(imports, split=",\\n")[[1]] 45 | # Strip out any version requirements 46 | pkgdeps <- sapply(imports, function(x) gsub("[ \\(<=>].*", "", x), USE.NAMES = FALSE) 47 | } 48 | # Get the name and version from it 49 | pkgname <- strsplit(grep("^Package:", readLines(descfile), value=TRUE), split=" ")[[1]][2] 50 | pkgver <- strsplit(grep("^Version:", readLines(descfile), value=TRUE), split=" ")[[1]][2] 51 | 52 | # Return a list 53 | return(list(pkgroot=pkg_root, pkgdeps=pkgdeps, descfile=descfile, pkgname=pkgname, pkgver=pkgver)) 54 | } 55 | 56 | #' Find package root 57 | #' 58 | #' @description 59 | #' Unexported helper to find the root of the R package. Returns an error if the path specified is not an R package. 60 | #' 61 | #' @param pkg_path Path to the package directory. Default is `"."` for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed. 62 | #' @param ... Arguments passed to [rprojroot::find_package_root_file]. 63 | #' @return A file path of the package root. If no package is found at the root then the function will `stop` with an error message. 64 | #' 65 | pkg_root <- function(pkg_path=".", ...) { 66 | root <- try(rprojroot::find_package_root_file(path=pkg_path, ...), silent=TRUE) 67 | if (inherits(root, "try-error")) { 68 | stop(glue::glue("{fs::path_abs(pkg_path)} is not an R package.")) 69 | } else { 70 | return(root) 71 | } 72 | } 73 | 74 | #' Handle the use case 75 | #' 76 | #' @description 77 | #' This unexported helper function internally handles the provided use case. 78 | #' 79 | #' @param use_case The specified use case. 80 | #' 81 | #' @return List of parsed information for the use case including, the name of the use case, path to Dockerfile template, base image, and path to assets (delimited by `;` if there are multiple and `NA` if there are none). 82 | #' 83 | handle_use_case <- function(use_case) { 84 | 85 | ## remove possible casing issues 86 | use_case <- tolower(use_case) 87 | 88 | ## define possible use cases based on the use_cases internal data object 89 | all_use_cases <- use_cases$use_case 90 | ## created a collapsed string for messaging below if needed 91 | all_collapsed <- paste0(all_use_cases, collapse = ",") 92 | 93 | ## validate that use case is among list of possible use cases supported 94 | if(!use_case %in% all_use_cases) { 95 | stop(glue::glue("Use case must be one of: {all_collapsed}")) 96 | } 97 | 98 | ## get use case specifications from internal data object 99 | use_case_specs <- use_cases[use_cases$use_case == use_case, ] 100 | return( 101 | list( 102 | use_case = use_case_specs$use_case, 103 | template = use_case_specs$template, 104 | base_image = use_case_specs$base_image, 105 | assets = use_case_specs$assets 106 | ) 107 | ) 108 | 109 | } 110 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | .onAttach <- function(libname, pkgname) { 2 | 3 | # Are you using git? 4 | isgit <- fs::dir_exists(".git") 5 | if (!isgit) packageStartupMessage(glue::glue("{pkgname} writes files. Best to use git when using {pkgname} functions.")) 6 | 7 | # Is this a package? 8 | ispkg <- !inherits(try(rprojroot::find_package_root_file(), silent=TRUE), "try-error") 9 | if (!ispkg) packageStartupMessage(glue::glue("{pkgname} is typically used within a package directory. See Vignettes.")) 10 | 11 | } 12 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | 16 | # pracpac 17 | 18 | 19 | [![R-CMD-check](https://github.com/signaturescience/pracpac/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/signaturescience/pracpac/actions/workflows/R-CMD-check.yaml) 20 | [![CRAN status](https://www.r-pkg.org/badges/version/pracpac)](https://CRAN.R-project.org/package=pracpac) 21 | [![arXiv](https://img.shields.io/badge/arXiv-2303.07876-b31b1b.svg)](https://arxiv.org/abs/2303.07876) 22 | 23 | 24 | **pracpac**: Practical R Packaging with Docker 25 | 26 | The goal of pracpac is to provide a usethis-like interface to create Docker images from R packages under development. The pracpac package uses renv by default, ensuring reproducibility by building dependency packages inside the container image mirroring those installed on the developer's system. The pracpac package can be used to containerize any R package and deploy with other domain-specific non-R tools, Shiny applications, or entire data analysis pipelines. 27 | 28 | ## Installation 29 | 30 | Install pracpac from CRAN with: 31 | 32 | ```r 33 | install.packages("pracpac") 34 | ``` 35 | 36 | Install the development version of pracpac from [GitHub](https://github.com/signaturescience/pracpac) with: 37 | 38 | ```r 39 | # install.packages("devtools") 40 | devtools::install_github("signaturescience/pracpac", build_vignettes = TRUE) 41 | ``` 42 | 43 | ## Example 44 | 45 | Detailed documentation is available in the [basic usage](https://signaturescience.github.io/pracpac/articles/basic-usage.html) and [use cases](https://signaturescience.github.io/pracpac/articles/use-cases.html) vignettes: 46 | 47 | ```{r, eval=FALSE} 48 | vignette("basic-usage", package="pracpac") 49 | vignette("use-cases", package="pracpac") 50 | ``` 51 | 52 | In the most simple example, running `use_docker()` inside of a package directory will (1) capture all the package dependencies installed on the developers system using [renv](https://rstudio.github.io), (2) build the package source tar.gz, (3) create a Dockerfile which builds an image with the package and its entire dependency chain, and (4) optionally builds a Docker image with tags set using the version in the package `DESCRIPTION`. The Dockerfile, `renv.lock` file, and the package source tar.gz file will all be placed into a `docker/` subdirectory of the package, which is added to the package's `.Rbuildignore`. The workflow is shown in the figure below. 53 | 54 | ![Pracpac workflow](man/figures/pracpac-workflow.png) 55 | 56 | For example, running `use_docker()` in the example package included in pracpac at [inst/hellow](https://github.com/signaturescience/pracpac/tree/main/inst/hellow) will produce a Dockerfile with the following contents: 57 | 58 | ```Dockerfile 59 | FROM rocker/r-ver:latest 60 | 61 | ## copy the renv.lock into the image 62 | COPY renv.lock /renv.lock 63 | 64 | ## install renv and biocmanager 65 | RUN Rscript -e 'install.packages(c("renv","BiocManager"), repos="https://cloud.r-project.org")' 66 | 67 | ## set the renv path var to the renv lib 68 | ENV RENV_PATHS_LIBRARY renv/library 69 | 70 | ## restore packages from renv.lock 71 | RUN Rscript -e 'renv::restore(lockfile = "/renv.lock", repos = NULL)' 72 | 73 | ## copy in built R package 74 | COPY hellow_0.1.0.tar.gz /hellow_0.1.0.tar.gz 75 | 76 | ## run script to install built R package from source 77 | RUN Rscript -e "install.packages('/hellow_0.1.0.tar.gz', type='source', repos=NULL)" 78 | ``` 79 | 80 | And an `renv.lock` with the dependencies of `hellow` (in this case just the `praise` package): 81 | 82 | ```json 83 | { 84 | "R": { 85 | "Version": "4.0.2", 86 | "Repositories": [ 87 | { 88 | "Name": "CRAN", 89 | "URL": "https://cran.rstudio.com" 90 | } 91 | ] 92 | }, 93 | "Packages": { 94 | "praise": { 95 | "Package": "praise", 96 | "Version": "1.0.0", 97 | "Source": "Repository", 98 | "Repository": "CRAN", 99 | "Hash": "a555924add98c99d2f411e37e7d25e9f", 100 | "Requirements": [] 101 | } 102 | } 103 | } 104 | ``` 105 | 106 | By default, `use_docker()` does not actually build the image. You can build the image with `build_image()` after running `use_docker()`, or in one step using `use_docker(build=TRUE)`. This two-step procedure is useful because other use cases may require edits to the Dockerfile to install system libraries, or copy in Shiny app or pipeline-specific code. See the [help page for `?use_docker`](https://signaturescience.github.io/pracpac/reference/use_docker.html) and the [use cases vignette](https://signaturescience.github.io/pracpac/articles/use-cases.html) (`vignette("use-cases", package="pracpac")`) for details. 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # pracpac 5 | 6 | 7 | 8 | [![R-CMD-check](https://github.com/signaturescience/pracpac/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/signaturescience/pracpac/actions/workflows/R-CMD-check.yaml) 9 | [![CRAN 10 | status](https://www.r-pkg.org/badges/version/pracpac)](https://CRAN.R-project.org/package=pracpac) 11 | [![arXiv](https://img.shields.io/badge/arXiv-2303.07876-b31b1b.svg)](https://arxiv.org/abs/2303.07876) 12 | 13 | 14 | **pracpac**: Practical R Packaging with Docker 15 | 16 | The goal of pracpac is to provide a usethis-like interface to create 17 | Docker images from R packages under development. The pracpac package 18 | uses renv by default, ensuring reproducibility by building dependency 19 | packages inside the container image mirroring those installed on the 20 | developer’s system. The pracpac package can be used to containerize any 21 | R package and deploy with other domain-specific non-R tools, Shiny 22 | applications, or entire data analysis pipelines. 23 | 24 | ## Installation 25 | 26 | Install pracpac from CRAN with: 27 | 28 | ``` r 29 | install.packages("pracpac") 30 | ``` 31 | 32 | Install the development version of pracpac from 33 | [GitHub](https://github.com/signaturescience/pracpac) with: 34 | 35 | ``` r 36 | # install.packages("devtools") 37 | devtools::install_github("signaturescience/pracpac", build_vignettes = TRUE) 38 | ``` 39 | 40 | ## Example 41 | 42 | Detailed documentation is available in the [basic 43 | usage](https://signaturescience.github.io/pracpac/articles/basic-usage.html) 44 | and [use 45 | cases](https://signaturescience.github.io/pracpac/articles/use-cases.html) 46 | vignettes: 47 | 48 | ``` r 49 | vignette("basic-usage", package="pracpac") 50 | vignette("use-cases", package="pracpac") 51 | ``` 52 | 53 | In the most simple example, running `use_docker()` inside of a package 54 | directory will (1) capture all the package dependencies installed on the 55 | developers system using [renv](https://rstudio.github.io), (2) build the 56 | package source tar.gz, (3) create a Dockerfile which builds an image 57 | with the package and its entire dependency chain, and (4) optionally 58 | builds a Docker image with tags set using the version in the package 59 | `DESCRIPTION`. The Dockerfile, `renv.lock` file, and the package source 60 | tar.gz file will all be placed into a `docker/` subdirectory of the 61 | package, which is added to the package’s `.Rbuildignore`. The workflow 62 | is shown in the figure below. 63 | 64 | ![Pracpac workflow](man/figures/pracpac-workflow.png) 65 | 66 | For example, running `use_docker()` in the example package included in 67 | pracpac at 68 | [inst/hellow](https://github.com/signaturescience/pracpac/tree/main/inst/hellow) 69 | will produce a Dockerfile with the following contents: 70 | 71 | ``` dockerfile 72 | FROM rocker/r-ver:latest 73 | 74 | ## copy the renv.lock into the image 75 | COPY renv.lock /renv.lock 76 | 77 | ## install renv and biocmanager 78 | RUN Rscript -e 'install.packages(c("renv","BiocManager"), repos="https://cloud.r-project.org")' 79 | 80 | ## set the renv path var to the renv lib 81 | ENV RENV_PATHS_LIBRARY renv/library 82 | 83 | ## restore packages from renv.lock 84 | RUN Rscript -e 'renv::restore(lockfile = "/renv.lock", repos = NULL)' 85 | 86 | ## copy in built R package 87 | COPY hellow_0.1.0.tar.gz /hellow_0.1.0.tar.gz 88 | 89 | ## run script to install built R package from source 90 | RUN Rscript -e "install.packages('/hellow_0.1.0.tar.gz', type='source', repos=NULL)" 91 | ``` 92 | 93 | And an `renv.lock` with the dependencies of `hellow` (in this case just 94 | the `praise` package): 95 | 96 | ``` json 97 | { 98 | "R": { 99 | "Version": "4.0.2", 100 | "Repositories": [ 101 | { 102 | "Name": "CRAN", 103 | "URL": "https://cran.rstudio.com" 104 | } 105 | ] 106 | }, 107 | "Packages": { 108 | "praise": { 109 | "Package": "praise", 110 | "Version": "1.0.0", 111 | "Source": "Repository", 112 | "Repository": "CRAN", 113 | "Hash": "a555924add98c99d2f411e37e7d25e9f", 114 | "Requirements": [] 115 | } 116 | } 117 | } 118 | ``` 119 | 120 | By default, `use_docker()` does not actually build the image. You can 121 | build the image with `build_image()` after running `use_docker()`, or in 122 | one step using `use_docker(build=TRUE)`. This two-step procedure is 123 | useful because other use cases may require edits to the Dockerfile to 124 | install system libraries, or copy in Shiny app or pipeline-specific 125 | code. See the [help page for 126 | `?use_docker`](https://signaturescience.github.io/pracpac/reference/use_docker.html) 127 | and the [use cases 128 | vignette](https://signaturescience.github.io/pracpac/articles/use-cases.html) 129 | (`vignette("use-cases", package="pracpac")`) for details. 130 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://signaturescience.github.io/pracpac/ 2 | template: 3 | bootstrap: 5 4 | 5 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | This is a resubmission for v0.2.0 release. The package now no longer uses the `renv` snapshot feature in any tests. 2 | 3 | ## Test environments 4 | 5 | - Local MacOS install, R 4.2.0 6 | - R hub 7 | - Fedora Linux, R-devel, clang, gfortran 8 | - Ubuntu Linux 20.04.1 LTS, R-release, GCC 9 | - Windows Server 2022, R-devel, 64 bit 10 | 11 | ## R CMD check results 12 | 13 | - Local `R CMD check`: 0 errors | 0 warnings | 0 notes 14 | - R hub: 15 | - Fedora Linux, R-devel, clang, gfortran 16 | - 0 errors | 0 warnings | 0 notes 17 | - Ubuntu Linux 20.04.1 LTS, R-release, GCC 18 | - 0 errors | 0 warnings | 0 notess 19 | - Windows Server 2022, R-devel, 64 bit 20 | - 0 errors | 0 warnings | 0 notes 21 | -------------------------------------------------------------------------------- /data-raw/sysdata.R: -------------------------------------------------------------------------------- 1 | ## read in and prep use case data to be used internally in pkg 2 | ## NOTE: reading from the root of the pracpac project 3 | ## NOTE: using base functions to avoid SUGGEST-ing more pkgs 4 | 5 | use_cases <- read.csv("inst/templates/use_cases/use_cases.csv", 6 | stringsAsFactors = FALSE, 7 | row.names = NULL) 8 | 9 | usethis::use_data(use_cases, overwrite = TRUE, internal = TRUE) 10 | -------------------------------------------------------------------------------- /inst/example/hellow/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rocker/r-ver:latest 2 | 3 | ## copy the renv.lock into the image 4 | COPY renv.lock /renv.lock 5 | 6 | ## install renv 7 | RUN Rscript -e 'install.packages(c("renv"))' 8 | 9 | ## set the renv path var to the renv lib 10 | ENV RENV_PATHS_LIBRARY renv/library 11 | 12 | ## restore packages from renv.lock 13 | RUN Rscript -e 'renv::restore(lockfile = "/renv.lock", repos = NULL)' 14 | 15 | ## copy in built R package 16 | COPY hellow_0.1.0.tar.gz /hellow_0.1.0.tar.gz 17 | 18 | ## run script to install built R package from source 19 | RUN Rscript -e 'install.packages("/hellow_0.1.0.tar.gz", type="source", repos=NULL)' 20 | 21 | ## COPY in the pre processing R script and run shell script 22 | COPY assets/pre.R /pre.R 23 | COPY assets/run.sh /run.sh 24 | 25 | ## add the translate-shell tool and dependencies 26 | RUN apt-get update && apt-get install -y bsdmainutils translate-shell 27 | 28 | ## enter at run shell script 29 | ## allows for parameters passed to container at runtime 30 | ENTRYPOINT ["bash", "/run.sh"] 31 | -------------------------------------------------------------------------------- /inst/example/hellow/pre.R: -------------------------------------------------------------------------------- 1 | library(hellow) 2 | 3 | isay() 4 | -------------------------------------------------------------------------------- /inst/example/hellow/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## get tranlsation language from first argument 4 | ## default to :fr 5 | TOLANG="${1:-:fr}" 6 | HELLO=$(Rscript /pre.R) 7 | 8 | trans "$HELLO" "$TOLANG" 9 | -------------------------------------------------------------------------------- /inst/example/ocf/app.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(ocf) 3 | 4 | # Define UI for application that draws a histogram 5 | ui <- fluidPage( 6 | 7 | # Application title 8 | titlePanel("Old Faithful Geyser Data (In Color!)"), 9 | 10 | # Sidebar with a slider input for number of bins 11 | sidebarLayout( 12 | sidebarPanel( 13 | sliderInput("bins", 14 | "Number of bins:", 15 | min = 1, 16 | max = 50, 17 | value = 30), 18 | actionButton(inputId = "color", label = "Add Color") 19 | ), 20 | 21 | # Show a plot of the generated distribution 22 | mainPanel( 23 | plotOutput("distPlot") 24 | ) 25 | ) 26 | ) 27 | 28 | # Define server logic required to draw a histogram 29 | server <- function(input, output) { 30 | 31 | color_palette <- eventReactive(input$color, { 32 | get_pal() 33 | }) 34 | 35 | output$distPlot <- renderPlot({ 36 | # generate bins based on input$bins from ui.R 37 | x <- faithful[, 2] 38 | bins <- seq(min(x), max(x), length.out = input$bins + 1) 39 | 40 | # draw the histogram with the specified number of bins 41 | hist(x, 42 | breaks = bins, 43 | col = color_palette()[[1]][1], 44 | border = color_palette()[[1]][5], 45 | main = paste0("Histogram in ", names(color_palette()))) 46 | }) 47 | } 48 | 49 | # Run the application 50 | shinyApp(ui = ui, server = server) 51 | -------------------------------------------------------------------------------- /inst/hellow/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: hellow 2 | Type: Package 3 | Title: Says hello 4 | Version: 0.1.0 5 | Authors@R: 6 | c(person(given = "Stephen", 7 | family = "Turner", 8 | role = c("aut"), 9 | comment = c(ORCID = "0000-0001-9140-9028")), 10 | person(given = "VP", 11 | family = "Nagraj", 12 | role = c("cre", "aut"), 13 | email="nagraj@nagraj.net", 14 | comment = c(ORCID = "0000-0003-0060-566X")), 15 | person("Signature Science, LLC.", 16 | role = "cph")) 17 | Description: This package says 'hello' or 'hi' or something else. 18 | License: Apache License (>= 2) 19 | Encoding: UTF-8 20 | LazyData: true 21 | RoxygenNote: 7.2.3 22 | Imports: 23 | praise 24 | -------------------------------------------------------------------------------- /inst/hellow/NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(isay) 4 | -------------------------------------------------------------------------------- /inst/hellow/R/hello.R: -------------------------------------------------------------------------------- 1 | #' Say hello 2 | #' 3 | #' @return 4 | #' Flavor of "Hello". 5 | #' 6 | #' @export 7 | #' 8 | isay <- function() { 9 | message(praise::praise()) 10 | h <- sample(c("Hello", "Hi", "Howdy", "Hey now", "What's up?"), size = 1) 11 | return(h) 12 | } 13 | -------------------------------------------------------------------------------- /inst/hellow/man/isay.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/hello.R 3 | \name{isay} 4 | \alias{isay} 5 | \title{Say hello} 6 | \usage{ 7 | isay() 8 | } 9 | \value{ 10 | Flavor of "Hello". 11 | } 12 | \description{ 13 | Say hello 14 | } 15 | -------------------------------------------------------------------------------- /inst/ocf/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: ocf 2 | Type: Package 3 | Title: Old Colorful 4 | Version: 0.1.0 5 | Authors@R: 6 | c(person(given = "Stephen", 7 | family = "Turner", 8 | role = c("aut"), 9 | comment = c(ORCID = "0000-0001-9140-9028")), 10 | person(given = "VP", 11 | family = "Nagraj", 12 | role = c("cre", "aut"), 13 | email="nagraj@nagraj.net", 14 | comment = c(ORCID = "0000-0003-0060-566X")), 15 | person("Signature Science, LLC.", 16 | role = "cph")) 17 | Description: This package holds the code for Old Colorful. 18 | License: Apache License (>= 2) 19 | Encoding: UTF-8 20 | LazyData: true 21 | RoxygenNote: 7.2.3 22 | Imports: 23 | wesanderson 24 | -------------------------------------------------------------------------------- /inst/ocf/NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(get_pal) 4 | -------------------------------------------------------------------------------- /inst/ocf/R/pal.R: -------------------------------------------------------------------------------- 1 | #' Get palette 2 | #' 3 | #' @param palette Character specifying wich palette to use; default is `NULL` to randomly pick a palette 4 | #' @return Color palette 5 | #' @export 6 | #' 7 | get_pal <- function(palette = NULL) { 8 | if(is.null(palette)) { 9 | n_pals <- length(wesanderson::wes_palettes) 10 | wesanderson::wes_palettes[sample(x=1:n_pals,size=1)] 11 | } else { 12 | wesanderson::wes_palettes[palette] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /inst/ocf/inst/app/app.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | library(ocf) 3 | 4 | # Define UI for application that draws a histogram 5 | ui <- fluidPage( 6 | 7 | # Application title 8 | titlePanel("Old Faithful Geyser Data (In Color!)"), 9 | 10 | # Sidebar with a slider input for number of bins 11 | sidebarLayout( 12 | sidebarPanel( 13 | sliderInput("bins", 14 | "Number of bins:", 15 | min = 1, 16 | max = 50, 17 | value = 30), 18 | actionButton(inputId = "color", label = "Add Color") 19 | ), 20 | 21 | # Show a plot of the generated distribution 22 | mainPanel( 23 | plotOutput("distPlot") 24 | ) 25 | ) 26 | ) 27 | 28 | # Define server logic required to draw a histogram 29 | server <- function(input, output) { 30 | 31 | color_palette <- eventReactive(input$color, { 32 | get_pal() 33 | }) 34 | 35 | output$distPlot <- renderPlot({ 36 | # generate bins based on input$bins from ui.R 37 | x <- faithful[, 2] 38 | bins <- seq(min(x), max(x), length.out = input$bins + 1) 39 | 40 | # draw the histogram with the specified number of bins 41 | hist(x, 42 | breaks = bins, 43 | col = color_palette()[[1]][1], 44 | border = color_palette()[[1]][5], 45 | main = paste0("Histogram in ", names(color_palette()))) 46 | }) 47 | } 48 | 49 | # Run the application 50 | shinyApp(ui = ui, server = server) 51 | -------------------------------------------------------------------------------- /inst/ocf/man/get_pal.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pal.R 3 | \name{get_pal} 4 | \alias{get_pal} 5 | \title{Get palette} 6 | \usage{ 7 | get_pal(palette = NULL) 8 | } 9 | \arguments{ 10 | \item{palette}{Character specifying wich palette to use; default is `NULL` to randomly pick a palette} 11 | } 12 | \value{ 13 | Color palette 14 | } 15 | \description{ 16 | Get palette 17 | } 18 | -------------------------------------------------------------------------------- /inst/templates/base/base-renv.dockerfile: -------------------------------------------------------------------------------- 1 | FROM {base_image} 2 | 3 | ## copy the renv.lock into the image 4 | COPY renv.lock /renv.lock 5 | 6 | ## install renv 7 | RUN Rscript -e 'install.packages(c("renv"))' 8 | 9 | ## set the renv path var to the renv lib 10 | ENV RENV_PATHS_LIBRARY renv/library 11 | 12 | ## restore packages from renv.lock 13 | RUN Rscript -e 'renv::restore(lockfile="/renv.lock", repos={repos})' 14 | 15 | ## copy in built R package 16 | COPY {pkgname}_{pkgver}.tar.gz /{pkgname}_{pkgver}.tar.gz 17 | 18 | ## run script to install built R package from source 19 | RUN Rscript -e 'install.packages("/{pkgname}_{pkgver}.tar.gz", type="source", repos=NULL)' 20 | 21 | -------------------------------------------------------------------------------- /inst/templates/base/base.dockerfile: -------------------------------------------------------------------------------- 1 | FROM {base_image} 2 | 3 | COPY {pkgname}_{pkgver}.tar.gz /{pkgname}_{pkgver}.tar.gz 4 | 5 | # Domain-specific stuff here 6 | # RUN apt update && apt install -y yourpackages 7 | 8 | RUN Rscript -e 'install.packages("BiocManager")' 9 | RUN Rscript -e 'BiocManager::install(c({pkgs}), update=FALSE, ask=FALSE)' 10 | RUN Rscript -e 'install.packages("/{pkgname}_{pkgver}.tar.gz", type="source", repos=NULL)' 11 | -------------------------------------------------------------------------------- /inst/templates/use_cases/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signaturescience/pracpac/5e426dce72ebfd129dec0ed2b0b7eefc273576a7/inst/templates/use_cases/README.md -------------------------------------------------------------------------------- /inst/templates/use_cases/pipeline/assets/post.R: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signaturescience/pracpac/5e426dce72ebfd129dec0ed2b0b7eefc273576a7/inst/templates/use_cases/pipeline/assets/post.R -------------------------------------------------------------------------------- /inst/templates/use_cases/pipeline/assets/pre.R: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signaturescience/pracpac/5e426dce72ebfd129dec0ed2b0b7eefc273576a7/inst/templates/use_cases/pipeline/assets/pre.R -------------------------------------------------------------------------------- /inst/templates/use_cases/pipeline/assets/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Placeholder for pre processing in R (if needed) 4 | Rscript pre.R 5 | 6 | # Placeholder for domain-specific processing here 7 | 8 | # Placeholde for post processing in R (if needed) 9 | Rscript post.R 10 | 11 | -------------------------------------------------------------------------------- /inst/templates/use_cases/pipeline/pipeline.dockerfile: -------------------------------------------------------------------------------- 1 | # Placeholder for demonstration purposes 2 | # Edit this Dockerfile for your specific needs 3 | # Note that it may be necessary to install domain-specific tools 4 | 5 | # Shows how to copy pipeline scripts from assets 6 | # Note the following uses the files created during templating and copied to assets/ 7 | COPY assets/pre.R /pre.R 8 | COPY assets/post.R /post.R 9 | COPY assets/run.sh /run.sh 10 | 11 | # Example runs a bash script wrapper 12 | # Calls to run pre/post processing in R 13 | CMD ["bash", "/run.sh"] 14 | -------------------------------------------------------------------------------- /inst/templates/use_cases/rstudio/rstudio.dockerfile: -------------------------------------------------------------------------------- 1 | # Optionally expose RStudio Server on a different port 2 | EXPOSE 8787 3 | CMD ["/init"] 4 | -------------------------------------------------------------------------------- /inst/templates/use_cases/shiny/assets/app.R: -------------------------------------------------------------------------------- 1 | library(shiny) 2 | 3 | # Define UI for application that draws a histogram 4 | ui <- fluidPage( 5 | 6 | # Application title 7 | titlePanel("Old Faithful Geyser Data"), 8 | 9 | # Sidebar with a slider input for number of bins 10 | sidebarLayout( 11 | sidebarPanel( 12 | sliderInput("bins", 13 | "Number of bins:", 14 | min = 1, 15 | max = 50, 16 | value = 30) 17 | ), 18 | 19 | # Show a plot of the generated distribution 20 | mainPanel( 21 | plotOutput("distPlot") 22 | ) 23 | ) 24 | ) 25 | 26 | # Define server logic required to draw a histogram 27 | server <- function(input, output) { 28 | 29 | output$distPlot <- renderPlot({ 30 | # generate bins based on input$bins from ui.R 31 | x <- faithful[, 2] 32 | bins <- seq(min(x), max(x), length.out = input$bins + 1) 33 | 34 | # draw the histogram with the specified number of bins 35 | hist(x, breaks = bins, col = 'darkgray', border = 'white', 36 | xlab = 'Waiting time to next eruption (in mins)', 37 | main = 'Histogram of waiting times') 38 | }) 39 | } 40 | 41 | # Run the application 42 | shinyApp(ui = ui, server = server) 43 | -------------------------------------------------------------------------------- /inst/templates/use_cases/shiny/shiny.dockerfile: -------------------------------------------------------------------------------- 1 | # Placeholder for demonstration purposes 2 | # Shows how to copy your app.R file from host to Docker image 3 | # Note the following uses the files created during templating and copied to assets/ 4 | COPY assets/app.R /srv/shiny-server 5 | 6 | CMD ["/usr/bin/shiny-server"] 7 | -------------------------------------------------------------------------------- /inst/templates/use_cases/use_cases.csv: -------------------------------------------------------------------------------- 1 | use_case,template,base_image,assets 2 | default,NA,rocker/r-ver:latest,NA 3 | pipeline,templates/use_cases/pipeline/pipeline.dockerfile,rocker/r-ver:latest,templates/use_cases/pipeline/assets/run.sh;templates/use_cases/pipeline/assets/pre.R;templates/use_cases/pipeline/assets/post.R 4 | shiny,templates/use_cases/shiny/shiny.dockerfile,rocker/shiny:latest,templates/use_cases/shiny/assets/app.R 5 | rstudio,templates/use_cases/rstudio/rstudio.dockerfile,rocker/rstudio:latest,NA 6 | -------------------------------------------------------------------------------- /man/add_assets.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/docker.R 3 | \name{add_assets} 4 | \alias{add_assets} 5 | \title{Add assets for the specified use case} 6 | \usage{ 7 | add_assets( 8 | pkg_path = ".", 9 | img_path = NULL, 10 | use_case = "default", 11 | overwrite = TRUE 12 | ) 13 | } 14 | \arguments{ 15 | \item{pkg_path}{Path to the package directory. Default is \code{"."} for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed.} 16 | 17 | \item{img_path}{Path to the write the docker image definition contents. The default \code{NULL} will use \verb{docker/} as a subdirectory of the \code{pkg_path}.} 18 | 19 | \item{use_case}{Name of the use case. Defaults to \code{"default"}, which only uses the base boilerplate.} 20 | 21 | \item{overwrite}{Logical; should existing assets should be overwritten? Default is \code{TRUE}.} 22 | } 23 | \value{ 24 | Invisibly returns assets per \link{handle_use_case}. Called primarily for its side effects. 25 | } 26 | \description{ 27 | Add template assets for the use case specified in \link{add_dockerfile} or \link{use_docker}. 28 | } 29 | \details{ 30 | Example #1: the \code{"shiny"} use case requires than an \code{app.R} file moved into 31 | \verb{/srv/shiny-server/} in the container image. Using \code{add_assets(use_case="shiny")} 32 | (or when using the \code{"shiny"} use case in \link{add_dockerfile} or \link{use_docker}) 33 | will create a placeholder \code{assets/app.R} in the \verb{docker/} directory. The 34 | Dockerfile for the \code{"shiny"} use case will place \verb{COPY assets/app.R/srv/shiny-server} into the Dockerfile. 35 | 36 | Example #2: the \code{"pipeline"} use case creates boilerplate for moving pre- and 37 | post-processing R and shell scripts into the container at 38 | \code{add_assets(use_case="pipeline")} (or when using the \code{"pipeline"} use case in 39 | \link{add_dockerfile} or \link{use_docker}) will create a placeholder \code{assets/pre.R}, 40 | \code{assets/post.R}, and \code{assets/run.sh} into the \code{docker/assets} directory. The 41 | Dockerfile for the \code{"pipeline"} use case will place \verb{COPY assets/run.sh /run.sh} into the Dockerfile. 42 | 43 | This function is run as part of \link{use_docker} but can be used on its own. 44 | 45 | See \code{vignette("use-cases", package="pracpac")} for details on use cases. 46 | } 47 | \examples{ 48 | \dontrun{ 49 | 50 | # Specify path to example package source and copy to tempdir() 51 | # Note that in practice you do not need to copy to a tempdir() 52 | # And in fact it may be easiest to use pracpac relative to your package directory root 53 | ex_pkg_src <- system.file("hellow", package = "pracpac", mustWork = TRUE) 54 | file.copy(from = ex_pkg_src, to = tempdir(), recursive = TRUE) 55 | 56 | # Add assets for shiny use case 57 | add_assets(pkg_path = file.path(tempdir(), "hellow"), use_case="shiny") 58 | # Add assets for pipeline use case 59 | add_assets(pkg_path = file.path(tempdir(), "hellow"), use_case="pipeline") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /man/add_dockerfile.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/docker.R 3 | \name{add_dockerfile} 4 | \alias{add_dockerfile} 5 | \title{Add a Dockerfile to the docker directory} 6 | \usage{ 7 | add_dockerfile( 8 | pkg_path = ".", 9 | img_path = NULL, 10 | use_renv = TRUE, 11 | use_case = "default", 12 | base_image = NULL, 13 | repos = NULL 14 | ) 15 | } 16 | \arguments{ 17 | \item{pkg_path}{Path to the package directory. Default is \code{"."} for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed.} 18 | 19 | \item{img_path}{Path to the write the docker image definition contents. The default \code{NULL} will use \verb{docker/} as a subdirectory of the \code{pkg_path}.} 20 | 21 | \item{use_renv}{Logical; use renv? Defaults to \code{TRUE}. If \code{FALSE}, package dependencies are scraped from the \code{DESCRIPTION} file and the most recent versions will be installed in the image.} 22 | 23 | \item{use_case}{Name of the use case. Defaults to \code{"default"}, which only uses the base boilerplate. See \code{vignette("use-cases", package="pracpac")} for other use cases (e.g., \code{shiny}, \code{rstudio}, \code{pipeline}).} 24 | 25 | \item{base_image}{Name of the base image to start \code{FROM}. Default is \code{NULL} and the base image will be derived based on \code{use_case.} Optionally override this by setting the name of the base image (including tag if desired).} 26 | 27 | \item{repos}{Option to override the repos used for installing packages with \code{renv} by passing name of repository. Only used if \code{use_renv = TRUE}. Default is \code{NULL} meaning that the repos specified in \code{renv} lockfile will remain as-is and not be overridden.} 28 | } 29 | \value{ 30 | Invisibly returns a list of package info returned by \link{pkg_info}. Primarily called for side-effect to create Dockerfile. 31 | } 32 | \description{ 33 | Adds a Dockerfile to the docker directory created by \link{create_docker_dir}. 34 | Allows for specification of several preset use cases, whether or not use use 35 | renv to manage dependencies, and optional overriding the base image. 36 | } 37 | \details{ 38 | This function is run as part of \link{use_docker} but can be used on its own. 39 | 40 | See \code{vignette("use-cases", package="pracpac")} for details on use cases. 41 | } 42 | \examples{ 43 | \dontrun{ 44 | 45 | # Specify path to example package source and copy to tempdir() 46 | # Note that in practice you do not need to copy to a tempdir() 47 | # And in fact it may be easiest to use pracpac relative to your package directory root 48 | ex_pkg_src <- system.file("hellow", package = "pracpac", mustWork = TRUE) 49 | file.copy(from = ex_pkg_src, to = tempdir(), recursive = TRUE) 50 | 51 | # Default: FROM rocker/r-ver:latest with no additional template 52 | # By default add_dockerfile requires you either to specify use_renv = FALSE 53 | # Or run renv_deps() prior to add_dockerfile() 54 | # The use_docker() wrapper runs these sequentially, and is recommended for most usage 55 | add_dockerfile(pkg_path = file.path(tempdir(), "hellow"), use_renv = FALSE) 56 | # Specify tidyverse base image 57 | renv_deps(pkg_path = file.path(tempdir(), "hellow")) 58 | add_dockerfile(pkg_path = file.path(tempdir(), "hellow"), base_image="rocker/tidyverse:4.2.2") 59 | # Specify different default repo 60 | add_dockerfile(pkg_path = file.path(tempdir(), "hellow"), repos="https://cran.wustl.edu/") 61 | # RStudio template 62 | add_dockerfile(pkg_path = file.path(tempdir(), "hellow"), use_case="rstudio") 63 | # Shiny template 64 | add_dockerfile(pkg_path = file.path(tempdir(), "hellow"), use_case = "shiny") 65 | # Pipeline template 66 | add_dockerfile(pkg_path = file.path(tempdir(), "hellow"), use_case="pipeline") 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /man/build_image.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/build.R 3 | \name{build_image} 4 | \alias{build_image} 5 | \title{Build a Docker image} 6 | \usage{ 7 | build_image( 8 | pkg_path = ".", 9 | img_path = NULL, 10 | cache = TRUE, 11 | tag = NULL, 12 | build = TRUE 13 | ) 14 | } 15 | \arguments{ 16 | \item{pkg_path}{Path to the package directory. Default is \code{"."} for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed.} 17 | 18 | \item{img_path}{Path to the write the docker image definition contents. The default \code{NULL} will use \verb{docker/} as a subdirectory of the \code{pkg_path}.} 19 | 20 | \item{cache}{Logical; should caching be used? Default \code{TRUE}. Set to \code{FALSE} to use \code{--no-cache} in \verb{docker build}.} 21 | 22 | \item{tag}{Image tag to use; default is \code{NULL} and the image will be tagged with package name version from \link{pkg_info}.} 23 | 24 | \item{build}{Logical as to whether or not the image should be built. Default is \code{TRUE}, and if \code{FALSE} the \verb{docker build} command will be messaged. Setting \code{build=FALSE} could be useful if additional \verb{docker build} options or different tags are desired. In either case the \verb{docker build} command will be returned invisibly.} 25 | } 26 | \value{ 27 | Invisibly returns the \verb{docker build} command. Primarily called for its side effects, which runs the \verb{docker build} as a system command. 28 | } 29 | \description{ 30 | Builds a Docker image created by \link{use_docker} or \link{add_dockerfile}. This function is run as part of \link{use_docker} when \code{build = TRUE} is set, but can be used on its own. 31 | } 32 | \examples{ 33 | \dontrun{ 34 | # Specify path to example package source and copy to tempdir() 35 | # Note that in practice you do not need to copy to a tempdir() 36 | # And in fact it may be easiest to use pracpac relative to your package directory root 37 | ex_pkg_src <- system.file("hellow", package = "pracpac", mustWork = TRUE) 38 | file.copy(from = ex_pkg_src, to = tempdir(), recursive = TRUE) 39 | 40 | # Run use_docker to create Docker directory and assets for the example package 41 | use_docker(pkg_path = file.path(tempdir(), "hellow")) 42 | 43 | # Build the image 44 | build_image(pkg_path = file.path(tempdir(), "hellow")) 45 | # Or construct the image build command without building 46 | build_cmd <- build_image(pkg_path = file.path(tempdir(), "hellow"), build=FALSE) 47 | build_cmd 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /man/build_pkg.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/build.R 3 | \name{build_pkg} 4 | \alias{build_pkg} 5 | \title{Build a package tar.gz} 6 | \usage{ 7 | build_pkg(pkg_path = ".", img_path = NULL, ...) 8 | } 9 | \arguments{ 10 | \item{pkg_path}{Path to the package directory. Default is \code{"."} for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed.} 11 | 12 | \item{img_path}{Path to the write the docker image definition contents. The default \code{NULL} will use \verb{docker/} as a subdirectory of the \code{pkg_path}.} 13 | 14 | \item{...}{Additional optional arguments passed to \link[pkgbuild:build]{pkgbuild::build}.} 15 | } 16 | \value{ 17 | Invisibly returns a list of package info returned by \link{pkg_info}, tar.gz source and destination file paths. 18 | } 19 | \description{ 20 | Builds a package source tar.gz using \link[pkgbuild:build]{pkgbuild::build} and moves it into a user-specified location (default \verb{docker/}). 21 | } 22 | \examples{ 23 | \dontrun{ 24 | # Specify path to example package source and copy to tempdir() 25 | # Note that in practice you do not need to copy to a tempdir() 26 | # And in fact it may be easiest to use pracpac relative to your package directory root 27 | ex_pkg_src <- system.file("hellow", package = "pracpac", mustWork = TRUE) 28 | file.copy(from = ex_pkg_src, to = tempdir(), recursive = TRUE) 29 | 30 | # Build the example package from tempdir() 31 | build_pkg(pkg = file.path(tempdir(), "hellow")) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /man/create_docker_dir.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/docker.R 3 | \name{create_docker_dir} 4 | \alias{create_docker_dir} 5 | \title{Create Docker directory} 6 | \usage{ 7 | create_docker_dir(pkg_path = ".", img_path = NULL) 8 | } 9 | \arguments{ 10 | \item{pkg_path}{Path to the package directory. Default is \code{"."} for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed.} 11 | 12 | \item{img_path}{Path to the write the docker image definition contents. The default \code{NULL} will use \verb{docker/} as a subdirectory of the \code{pkg_path}.} 13 | } 14 | \value{ 15 | Invisibly returns a list of package info returned by \link{pkg_info}. Primarily called for side-effect to create docker directory. 16 | } 17 | \description{ 18 | Creates a \verb{docker/} directory for a given package. By default, assumes that \verb{docker/} should be a subdirectory of the specified package path. 19 | } 20 | \details{ 21 | This function is run as part of \link{use_docker} but can be used on its own. 22 | } 23 | \examples{ 24 | \dontrun{ 25 | # Specify path to example package source and copy to tempdir() 26 | # Note that in practice you do not need to copy to a tempdir() 27 | # And in fact it may be easiest to use pracpac relative to your package directory root 28 | ex_pkg_src <- system.file("hellow", package = "pracpac", mustWork = TRUE) 29 | file.copy(from = ex_pkg_src, to = tempdir(), recursive = TRUE) 30 | 31 | # Assuming default behavior then docker/ will be created under source root 32 | create_docker_dir(pkg_path = file.path(tempdir(), "hellow")) 33 | 34 | # Alternatively you can specify another directory above, below, or beside package source 35 | create_docker_dir(pkg_path = file.path(tempdir(), "hellow"), img_path = file.path(tempdir(), "img")) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /man/figures/logo-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signaturescience/pracpac/5e426dce72ebfd129dec0ed2b0b7eefc273576a7/man/figures/logo-large.png -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signaturescience/pracpac/5e426dce72ebfd129dec0ed2b0b7eefc273576a7/man/figures/logo.png -------------------------------------------------------------------------------- /man/figures/pracpac-workflow.drawio: -------------------------------------------------------------------------------- 1 | 7XxXs+PGtfWvUdX9HqxCDo8AQQAEkUiCSC8u5JwTgV9/u3nOSBrNWLZLkn39lY80h2QD6LDD2mvvZp8f8FPzksagz7UuTuofMCR+/YALP2AYijIEeIEt+0cLgyIfDdlYxJ83/dzwKI7ks/HLbUsRJ9NXN85dV89F/3Vj1LVtEs1ftQXj2G1f35Z29dej9kGWfNPwiIL621aniOf8y7oQ5OcLclJk+efQDPl5oQm+3PzZMOVB3G2/aMLPP+Cnsevmj3fN65TUUHhf5PLxnPg3rv40sTFp53/kgTw7Un4QzqrTB53dNfp1N/6CfnYzzfuXFScxEMDnx26c8y7r2qA+/9zKj93SxgnsFgGffr5H7boeNKKgsUzmef/UZrDMHWjK56b+vApmPO7u5/PvDx788CP55aPw+uVFYf/8lHbtLAZNUcOGU7eMRTKCyevJ9nnxc0QUB58/FgZX8zcF9mXxoKco+Q0p4Z+GF4xZMv/GfexPagX+kHRNAiYPnhuTOpiL9et5BJ+Gmf1038+6A28+1fdPqPJzkmtQL58jeWBdZhBV0MRB1xhVg8nzIRAZlcF3P9Cnv/wFPCKcH6f7xbQuhv6dmz5boHjfflZ347t7ali6jxtwBPyk6S+bvh7gx3u4FHVcZG03Jn97HnEXVVCh35sqRgUNMC6+Daf+/Rn5XtNnR0DD64816O33diS8Z5QW9fdm/U/19Atd/BX9EQH/AWv6MTt+b7/BNCXz9Htk9s8N1/c/3v81Q/Vj0o9d9OOU/2vGi4M5+JDjr+74GBxc+nCBr93iWysu2mn+TXdrgvY3r99/vwd83d7saTv9DrV9r89lLup3n7+KHl/Hhi0v5uTRB2903QBD+DoOBDWABPC+TtIvEPM34f0T3ZJxTl6/DejfAvDnA8SXUPzJRVDi8/P2c2THvrTlvwjq2Jeg/oeDNvnvCL//gJz/kLBJ/INhE/93hk3iTwqb/0WD/+NogKJfowGGfIsGKP0dNEDxPwsNqG9scZmSv37Qov/5f99IF6x9/lqE0zx2VXL65Ght10K8APSl/lXTF1FHQFZAqjgPJVmArIf7vNAUcfwGm+/p7Gut/n1S/jnjL9DzLUn/XXrEUeprPeLkN3qkvqPGP02L7Dda/EZzIBfs4duieaefP4lfDcKkNrupmIsOqiHs5rlrwA01vMADUMrewv+izjhJg+Xtxr9W4AxjAR9M/UdSnBYvqDD+PSD3pRX50gK7gvQH5z4+YmLfZj9gp8LmjfuGXKWs48CP/njm52cG3sF/3IU7cR54FZBEtyjYwsv66WHfLicuu6RcXhWwkau3h1gf8Hl0A3fHEX8rijB5P1AjdztHnhjbxHKcR82Ti/EYV5t6CXC99Fy+Vht298l1MXkwhxPHPZ533paLiI5RcpIs14UIuUFHFel1LvZRfwahTfoKZ4jEdpK489mTOc3m98I6a1xPXJiqUu5lPfS3kHsaOc9120W8noOhky6FELp8LvB5eNOzqZRuJRZ3eEDfPXQkvQtWG2CcpUgaKz2cfvChreAUu6yAyfP2I5ZqB7TE4APNBjZtLsAagJXzJPs6hvHpcFduvROvGCxlS4EEOfl+gcsSsgrIh8u5G8cZBfh1Psk8EBClvEUYn4HEu7MG1s8jj7cCCHBFuF1uZ/DSgYd5Lt++PPzZ7ZUXoKbeD1844wSuIKfzT93eLtGXhz+7PWW37b9z+rfPyeoHmrsY/TAaD1l5Va2GzYZh4QpejcWNrtjDd8aZnE+Kb6A2GvSKWvV7uLvrgp9urQ8s8ApsDjvqOd4dsgsdLKE2ivOCAZ1HT3deD/vBqszkVTgpVPqzHc7qGFeJBQbY8N135jlI8LFSnadtx5XW0nSHKmuxy2IvrI/P/hWDVZgCUM0HvPfKveym50TexsmyupM6Q/e7nBSlou4xmWDrXIWV6lFgwnbUgMejqnKPo+188sLks5KODzReQNdRfhOnzn9eONFFS9Jk6v2xuukpf/v4fkYAAvM9WFPAyLLfL+MwsTqzepVLr9kN+qGzpyu50qHT1uU23c6ON9+4p/hsFbO1FQn0khC1GbfGXR9D6K+AgfLqMXHoE/Ul371C4FfRiT67fluVCY6WFJMnESdJDwSukMSi3YW8jawqmrwrCuzDHmkTmSh5X7qhAtOeZ2Vte6XHN2oP2XEM0fKRRIJxepQm53t2AGbdnycajH1NlROsVvAJNoDfozQY/V3Jg90f2p6sYE0ESBre+XAVpHoLYIC/h6G3wUUqC7sXq66caNki0LjW6zKUcLhrFAyf4mEVEJ+afZiHOwkkwFurVJ61ljrm1FvvxwZBVCTV9abf7CSumLF8NnwKB5br0U5rEb80g1G2Co2lUQEF9kjFGQ4eCRf9xlVOcomza0eZU5NCeTSn3j82QiGvzDg6GGDBvObe8rCnBmOXHsseJ6Ybzc+3NQE9uHhZIxdEjWs8k4k8OIyXxSplhe/00FF+8jph4gXcW+sOCPD8KbHuAQoXSY/Aufiuzi9cP7FhCtv2o3W8oyILdToFwzAc1XAUappY1XoQ2PnJpfZ6PZ95OH+XdCjU9O4VUy3hA51bc7rVxQQkL1cLkywE3U7CqpABhpYEGp/0U6uGjPAU7bSZHfZjLq9Nh7hvbIVzPfo0V1Pd9WJojMZFERg0zkVuJb0Xb54wxX+V7sy8VhvXwA1tPtrAlnh8Z2MzZyKubkg1RUQHfdbK2vR8jk/AgmxlrMbdUxTXP649o95E2VwvisRemGZSDd4rG4ZmwCSwSDXmY3kbpeYb0rCQelRPLR3WkinKpFV1zEqJGZdj+2juYlyBq3D8BlFU8OqRlzgICHaFzyvIPWRwJW1qRUq1xCfwe8Z7IqBcou4V9G0l3QoHnkaX0IBOrBbzJttWbd0y78An+txdz4bQp07GSY9AAyyyqEwF3wU4lN6mwZGq9qG0RLsnbz8INBLITnRs23WLVoszPTrotZ2YisHKtXUdaBtOM6WHZIUUtK2IQffURZ/u0VZaphkYepOzomzplE1y+nrDcHdl3f1BRgR5yH5axa6J+LjNHP7B4mwfJGzKr0ODsk3WlDQwziQlxRGJtYjnQ7viGpkK2iWBAR/6D4LOJD5ApUdOOxG7C2CyuW21ZvvopDEm817rgLP7IMLpAfyjHhZIvYC0A4mhiVONMR3NGamGQTebc3bOk3rFLrgRQdlt0MrHy2lYhrNiGhO9lha8B7QWxzsphLwiIno4hXiS76F0E4w2MFcPaRaZLfGhbA0KlTnagcjSQrHPEp+G+JpJaG0nDJeq0N7nDMchmJzTqZpXXGcOg4ms4alfLFwOT3MoPbnGiDBaR0rWQslypKa8hU/kQAY3IXGW+5RxKlf834jEp7yjxqGn0w5TUh1J04McynED0kjYM4T7FttkyWShfb9FSO8thEuFdEiFruo9JHWIWOjR8mbnSsBqNtdLc1KLaqzJHgKdTWe2Qz0x5KYTd91THle4q5ZVkr7hQe1yj/zMddD6cR/3tgV0Lj6Hjr+DGXIp8DfhSo6jizbodeDUx5PjNAhs+LRD9wNvhWwQn/xpB4u9pCM9bQYP6DhvOrm9OYBmeKK87Mue5+dYGm+D9wBymuchNh7c0+cvmpxj6HUpIiATrXPsQ9Pz4wU6EF7abRHcLOO2U98/HNokGwHkSWFwxjQHpPESeALLyLBCGzBmz06jhZB+I23ureI2OcAMppoxK8E9SdsguO1eHknDeTtxcXqf2MwRQLik0Lm3vJcnDQjUzbiHyjWcysrDdT7ADrvxQn5itoBrQUTnd6tpKeUJKUHsCBC+SRU6QKaMjpumsP3xzFwYxkwf0m8at19Br65SuFyOErNIzZ+EzMMhaZFHbcNO48F3guVgk6kGgzfZrl3vKYlowoLSlqPWc2Y4yzwAnI0m4B89iPADmXhCUw5DFzfP1yZhilutUZvYzqMgL9HkF0Mjd9R+w/3VwPcSRAOf8SbB8FFUz0RO1slpdDHhWT/sKKqT57PxEpAR2AQJS8fm6cT5guyjbEqyK7YKwEIH6uKvq6QfT6edMcp2T9Dox0vbjybMwVo6YN/eD+mIuWar345eWdELJ6uAYrWHMUs4Yot23DI4T/Ahn4nPktNOQbNTGcOKPUYtgzXBKBRvBzYrMQiU5YCdHqmOIWEaBoPZt/qWCA7ghQvpe5cz8KZYGijSIG2E9l3bx55ppMkSu5I7Rb7K++6SqtIDGORLhdwqqZ354rFB6yBB1GAgLMTz58jt5MnCMjVzdGGidnGPQG+HYiip+xHJWTyJWWpb9v5slv1089ayVJxnndSGlVtqGON9qyR5uIYUygaz3IL4wt0ONUbXZO0xbB3KDm0Qkx/Ia7QmW4AymfIIyQwrfWq9dkZ6UnOsuOOnLAxv6DOuEyus2hfgAkCeF18vu6c5sq6v4iDMoujcsQUFwjQnNEjd04ZWp9Y8J8B4KB9Ea0NafT2g7mfAbJHwPrNGxm/JiVL0BEMXMopahMmpYbBONGWSdFVlkgwozwVHfdsk18qnYpzaPAuoeEbsntY0SCCbt447sXFxmdBeNUrnklXR+ys2MBt9stWEjpJ4hIDvj9TdxkJ1dKAns1WEcyEx3SqTVKsEWFOC9iP9qqkLd+acK8CvTIcMoD3AAMOgHRdaUvYXzY/ufqoVt9TnBHXtG1nFgkEmSWePwCKhS08VXdGvbfNgFhv5ajoGXfpIL9aNCABmA78MLQckITsZWrNt263feluiSXuAbmW/2yT9bn+a/moyWTOHMOJHyxpd0Kfopm3ycKt8J+0gBsxFV8wxGfB2J3bUUzmNvwYUHtdMmwjgqWlGUBSYoH1c5byuZsN1Ey2AmVCNHKFbAwi9b9u6nyxP5Y0Ev0y26LCXqFr9oxkjlej3h67g7YVobxA1l+DoHhDpmUrWaTKBTBfSZ+fG0nerRvFSauu8glw5nEdsoFZaWxsdkmP2Mron0WaTXgvXaY22dqGBtgPgOB/BGL0ZG8BVse/HBNdIs+1NliaS2okbtsaFS7g8prpKp3DsG2C7Y7dOhMlzCBCM1gzUnFU001fTTO0wWrzsNIT5kMuvuHuGOUfIkBUJUoskqaLUZj3b0Scq7Z8Bhm/6dCtR/9BY2oRJCQrk0kFqgqFqjKM9jbK5yOh7o8GYtz5WRZYgdV6HVQM0u4hmA3pP4cMEy7Pf1wD/XXbjnkc6jha+iDM5smEEPwrVE7r4h60wItfwHoXJ6SnCGGk/GGOeY0OKB2IoiGh6LcIM/DAS2mJpIQwdFkxOAfuqMJ7QXV95mfEyLz4Go16dChuIe3G/NEzNw2Aodmmo0ymM2StgZ1N14zSO73vIZ0L4GzPZEvZb3jxaKxvPFHZLDFePzI9+wUZ9JD3bjWs2x4Q76SdEIJPlC/od0TOVCUjQigMu8AD4feEFnUOkGaHDD+IaMG5ty76shizD5zmgyVN+vm3kRocpixDEbMdAtTwBgHIKQuCT4o1lrmjZpgxnkAoT+BfTZBkccpiI7BfKXY/ZxGHFtRUJR9XWxdge0sxFrAb3l8RQvM9JANLj++HJGQKoNE1HARS2g8adRxHIHWCTpN0QSUfwEEaI5D1HkBgQDbClHcYp0Pq6oohSaxDlpqx4rCBZE3lqoMZQOybPVGWOodk+i+IgF96ZeErD/GPzeE4SlL4xDh141/Lu35yRnu4VGF9Xy1URppjjE742YfK6MO0zBHkM23oQkTxBIHxpnfeb3CaxHdmPILVW9XhYVEKqG/SzvZNVQ2SaEzUNq5GGiNZxNz67AzwBKcO+Oy294nMyUkNZoXsAYwSyZqw5PxLCb9RqfOWQdCw0tL3BCTlCQoF1mOvEvOiZKOt+scxLLaGxM1771EqvI4gHR7kYCVzmjeQIQeZUyMzLTVr4juGn1rI887TDvmrMGlFIp1c0NPzDlD1oCkpeLdOx+B5MYFP0KK6AFaV4nz1YB73VSgi8miCZ/J07LG9JDmp1UnndsuIPaPDidzws3/GQf/EXIy+nV3U1ghrM7wXXtJpAi+W2fzxgAQo6oXUaXBH2acMktY1qArzkQmvqjMvfTpwe6wFGuooDFPrGsiiu4/JEXLU5nnEqlpWWtFeKDo8RYdBjaRLTO+ipySCnBhxDMj30HeHvWLoHhsJvgXA6TKbaMZmci2EpLUW9xeEipXcecOujQtsVZirsbCCo7AF84e8WsNRtKqTTcW0XRzDR2Ubf6YzqJ+v+8gzsBpi8tiHpE1YvaBBJR6qjNH/BHZy0lW49TlwoyEpaMVQ8X28wFaDVWHLY8o3wNhqtnpgRCE+kIDoyJR/GEcdczlX2ggIxL7mOoVGSd9E+OMsFRmcYbk6fTHqMlTNj0uYiCTrD+Hf7LWBxxvqhgYUHRnTHtGWVnTb2+xVD76TJHO/45bS+6cUZagdGG0FFBsa7NtNrbX4lCYabszEdjezkzmywyHOJG7/gmTBNvJCM0LpcKhw4FofQro72CeKiAiKNZ0aqgy24GnLRPEiDUWBHxsi+xwZon9O6f6Z5E7vtIDXmuXReJlgxxpakkguYo0cf0ekO/NJZ95UTIz9jzmspArYO8+eN4Dn11IAM2QTcRg3iZRxqysOAR92jZYnukJjz04nC0AN77MEMk01Y04tt1IO4/2C0qfIh4zNeIAvRS8dpFujP+buvidZoYZJUNF/yYAapGZ+pTxRH6WnlkWMZg+HQysmLVedVAwO4iXEAWQDA0An6FH8A9OJB/AYy9m/bfGpdOwjACqcy6KnQV1IgT3YA1MF5+b7Aziqg223Vo29GU5awiOg2wPYgeMWhlNqnm8AZZVVvZPzAvVHs7UwYQ9tU0vop1OU0Bn1STZAl20D8PVY+NSABbrsBxCoBR1RkGBUANg9DqfikcXC8RyZNIo0tBvJWYJZi86Bwg5zfMVJOUCrlOy1+8zYzq2uyPsMMVLUinR8BeYPqxlaRtO/7g9SURdiiCYha3HbAEjjh9ICMWxFiE/sIN3PiIsABaQNmZFNhyeqiRjAXHpW819T8hc3UAVMJZzBKVVH85pzZEUSbRMrJF8exa8zl0KZ7GBm0AnpAy9V3iHxTFJXFvp3nurdTl5JonMdDZ52PynUguQmBtU/YntrK+IZWk0C4iS/m2WPqxVEnox1vzrpik7NwSjryCvClCaZiuNK2MtcsvKJVd+8FE849/e1sXjK5m4KAPAsmtOcUH6EAZjMMOqeG+uRup8ylIAPrYJVpfB1+CxKj36is603kkDc7iKokcTkadgwrtjC1lEfHsUF+tFheFkPNoYDZe72Mctclhx5B6lcev7MDyMb3KnUmbQM5CcUazCDxacAq0VydArTGaYTctzeq1HtCJp0EeljLgTSYCBNSgQG5dJdqZ+4fqF1oQEoJlwHWD4LekW8GxDHUHugIzEu9XN7iYwkbOK6f4Z/kchM655Vzv7nnYHLOtjLRjsX2WhiT6zwzeazWHdFO75hC7ecLl4B5hbMeLEWBsjV1Rdl1eAWg++Zx/mNrMXOlRxcDIButODeDprFhpgG1YcmqmqHRwZojU3JpO2kmrFA9aJ4OpmA0YBAUcJyhHYBxObn5YJjyFkBOJQlpcCgbRCoZXE3AMzmBjw2CVh4ZkqBdfMgqv6mUq9A1lYVPLtai2x9bg0oobMO2oaKvkEmw7yTLkjvvzZpns6FgaAyTSG13eMMBgweKBNAsX4ZH6MjFXbjq9GBBroTfvYjFjYepZuILcw4TX5IRR7L97qrX7J4tx+XM/QnVNNTQsc00CQxgrEyQ8UsdxqFhFWaiXtTGe4Cm88SfsruG3WiCUfdx6Al/ITJDFlCMuwmjBTozT4nhgieBF+R/7I4bjS78mgFyStIlRES270CS+yLwM1MlKc6tJU4Vu9UKy0HHz2ZJCGo1rXZrOD6mHgQ36+GSBNQKSL9h3Ch2oG0Mex3sgir2wRz3NICoGxACKYFF/pG1zqec0NydPtvPClZ1XX18yDAO2EqZtx60vl4UM/V2AciVsWHwrv6OiyGPljPmC813p0S6/fac+o13biDzguHkjnL09LmfRfBHuzZKh+9wkx1WRfwO8jhL2H2S0wSY0VHYfr5yaZZxMHTI4iOGePaODsLjSYUE+xIOcqr6PSaDC3xakZoZ9+eqLTgp0jMN5AB/VzrJ7bVlIIdRtdaEw8KM44mts+otk08COuAwteHMGUsnCNqQgS8g3Ho/c2wLspwCkjEN1hRhriiglOVfVH4FXMuXRM60Ln8Htxn+xpUD1YOsBHL1yZyOMXRGsa5MTddabX31sAgAEDcqCmB02zoaFtwhcbl11Rd+Q6IM+BlLsPFGXXwu+/sWzORlBtMUyiGlSWZbtKo8GyH0xZLh8jeawVSVu3tXqBiDgcm+0KJkJjuzyelFNqc+V5VMtBBEAtFeVifJMx0EXSgFZ3TVg7z8Nyyzza4ck6DXaAK+0THLlJ8oWKS+s/VumeqwXe/Fmdu0PAiOxXIFH5PTB6BPPoseovCP2L6cmVF37QHHusHJf+rUATrlAec7DoBHNasxA4jPEEAdZ5zZKnU5cV1XYGPHZLAy0MCFPp052njvVb1ZCYM+bg8O9plICh9ptLlaD5BXzB3uYPW7PhksL4o46QJ3/o0oZl2eG7dNQ0ddp5rk6HUNBzyeopyax6qFstiUyp0c55ZNj/oZw46tQjmxerRUfLoRDXAQ4qjmnWJg/BX2iAw9CQ2lZZ4fi0XzBS2UeYRszMk4VPrSws2weTkZK5TTeb7Zvq0QOnt4MUtUIbPtsayO5whaqQj40wG9tIZ2dgYaUFTI+3lRxUBqAlYhD0FvHEmgwi1c6JHWC3vNZ60Bq+UE0UbTU5BiTRpPJXu89IsDDaITZzt2bw3xsbl0qKvaKkH7GuY5gN4YUBjIYvNKAVQwSquYSu/BAnfPxR7uCBNDSR18FLE+AdkXibq2sYWY6TYGQ9A9SfUD+paFCpLy9boSn3uEdxbIJm6EdExLEgmbIWnPWErAAoaumDBh0BLd0fvrUNzncqXamfUSqbWKYZ72VJg9tm2I2QoJIz0NIE1pj4N2Z91bJPb6Glq/KtsnR64tueG+ZQdxzVAp8aDSuYbywgOMeiHAG/2n7WO39J6iHKQgmE+8PKzu7sAwvAXYgTLVYuNid3OPR9OsFxuKTNoVCtCQGrOwxdwN5ZwYdr4BEBCOAWZB+Aw5wHLR2r28esvrGrXGQyLNaDJkU4J+wVEDYKI3LFgT1x+rHszpglfYfsd8d35v9TEujFgAQUDaRUF8Us7NQA2W1rYvPEh8OlZSEVMqwZxs8yPBXRt07nOTvMzQrIHkjMOxdjx4vRJnzkuDObB0g7WOzqt0OnTkGu7BFsBPMsG27GfcTBN9HWWZPNEXMWHmw+FplYRVnFey07uw7+MJ+I/HLwFdwL0ZqwnMHVYOn6RSvobCVJ5kra3zjOw0hcZr4uUJksMcb7p21IXW8GGsqt2rynryK2ZGZ5BLQWvtAO9b6NejnAhYHfKBrUaNn5LQs9bh0luir8K0eDlUsk6ReaST6h6Qiwq/U8WDu91TaL4iJqgPdnzpV8jL5HtjnpaUvIYOluulOPo2rGnwQQP8SPEJh3YW8yC1tF7CC1u9loNShEjD43wxXtPBqRu7B8CMYIB0ClJlGkJkaYTeT9sykTbPxu/vcZh0cbtmdIqRFw2P3EUEU4TsnahZwLE4bXq17gt3mzCdxrc/l/YzataXvu7dK2QHBAoSIXBJQmhCtN+lHz4hz5qwQMacCKMvRxghp24NVt2JdakN2CatPbBO7YtE03NZV+FunWDJwW/CeBqhL89OEDKIDmNPZ8Oq9FxBs3ouR9JGXcIaD+A8R00nbB+62HEp0PH25DkxH23XZhRoK4FP4Gp71EXwPKKabVWMpeHe1X5oqtMCuwzlr/HjtbhmQeM1rI76W9vOJEaksnp9tSAGburzwOsSlmsmmC77NFGmPqbTWm8qMa076zivh3b1DovxvKv9RO3GX+9rrDwbQ6LPSDVER+NuOMWaTI/AvVMMJc6cJPTDMiCHSKP0ALL9nHrpiho/ElqjFXcNS5MMW+TCaSdnuHbWtL4Lnfm0Mk2iOXxnscCXPfyGz2zYuFADZ5BGgJ8AMGRtd578iI4lY1gyGTab6ad+GQuAdeREPB2CpGwcd2ATBMCEqKFNp1XyuCDRWBZ38tIbsH4z7Da0FyKdDt5ptk+PXg5tnfK0NSzv/JxK4P9V/d4T9qEqLVJYpDOITxfURbPplbAJ/VBOABn5wdrdGPdNb34DRRBAYDUcfh4fwMp1W7cVzIUFWDcKoYt5q6SeEWWhjbdEGmo2Lbvx0rvIXYSZ9oshaVZsZYtUw0hFkavsoPEdd4DnZD598BO3vusE6FK/Km9tKHlubMCit6L3YfVPn2bzEtuhDaxTNmH0uUE/yKkUuM405JyxY6YPvxivxtk9JQ/i5Uv8fk3CSODrvF1INWp1YF9QRtF0nLx6P3OdnMIqKfthLThLBtiBFmHI18CXMxGPa3a9YjNtOXGH+tLlBQiV4OwpBvccgoOxAgx9MdEkiIEOq186wTbQ0y34nRQ+wQUN0H36fDtxdnp/KqU56TgWhBQea5ifTydoW9x07AGphrq7jqtACqvnZBduUuxA6c3VB/Et/oJIhLHAqtSl3agJUptyoRqUnR5efpMOeKVzu+rzCi2h7HyCV2QLWIO+9hNboCuskglXSH8Sw7kXmwNCP2skPjDnhi1wSHt4ZBZw5AZmxs6PO+6vbxtAQbCANgA39Tcuu3HArYCjA6vDyOVjT1/mgnIa9pTxlq3MKvVWAbuHjCJhEimMKk+dhg1HOERhylcBckHxozrz//H3Lv87p6/mdLuhhAG/6X2KlftZfP4zcwSGBAEnKfWSlMvlEr6H4pX7kzyPlZJlGfwmPPz/jzgDwCC/OsuBfucsB/q9sxzon3UKAMW+OQbwPgT71/c37P97muNvaJL+tSb/3ac50G8PiAmf55SRz8MSf7giP09A/U41/lIzX6lVTuo1gT3/Qc73q4NUKEt9ozL2X6oy8huVfTlajoxL+/VpbHg8/ccf//OU+Nu++Qc4IvW1VvHvHI/76W9b/GvU+u3xuMcctHEwxh/nJMHqPlX6n6bMP9gjfzrY/MUj6e+Ew+8ddP7zdEd/o7uvFAZmBr/Z/Ms/AYHExZhEc/de2P8U0G2/+jsSf0YE/c9SM87+Ss3fYT0/ue3vVDPcif3pT9W8r/3iD/7g5/8F -------------------------------------------------------------------------------- /man/figures/pracpac-workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/signaturescience/pracpac/5e426dce72ebfd129dec0ed2b0b7eefc273576a7/man/figures/pracpac-workflow.png -------------------------------------------------------------------------------- /man/handle_use_case.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{handle_use_case} 4 | \alias{handle_use_case} 5 | \title{Handle the use case} 6 | \usage{ 7 | handle_use_case(use_case) 8 | } 9 | \arguments{ 10 | \item{use_case}{The specified use case.} 11 | } 12 | \value{ 13 | List of parsed information for the use case including, the name of the use case, path to Dockerfile template, base image, and path to assets (delimited by \verb{;} if there are multiple and \code{NA} if there are none). 14 | } 15 | \description{ 16 | This unexported helper function internally handles the provided use case. 17 | } 18 | -------------------------------------------------------------------------------- /man/pipe.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils-pipe.R 3 | \name{\%>\%} 4 | \alias{\%>\%} 5 | \title{Pipe operator} 6 | \usage{ 7 | lhs \%>\% rhs 8 | } 9 | \arguments{ 10 | \item{lhs}{A value or the magrittr placeholder.} 11 | 12 | \item{rhs}{A function call using the magrittr semantics.} 13 | } 14 | \value{ 15 | The result of calling \code{rhs(lhs)}. 16 | } 17 | \description{ 18 | See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details. 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/pkg_info.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{pkg_info} 4 | \alias{pkg_info} 5 | \title{Get information about the current package} 6 | \usage{ 7 | pkg_info(pkg_path = ".", ...) 8 | } 9 | \arguments{ 10 | \item{pkg_path}{Path to the package directory. Default is \code{"."} for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed.} 11 | 12 | \item{...}{Arguments passed to \link[rprojroot:find_root_file]{rprojroot::find_package_root_file}.} 13 | } 14 | \value{ 15 | A list of information about the package. 16 | \itemize{ 17 | \item \code{pkgroot}: Root directory of the package. 18 | \item \code{pkgdeps}: Package dependencies from \code{Imports} in the \code{DESCRIPTION}. 19 | \item \code{descfile}: File path to the \code{DESCRIPTION} file. 20 | \item \code{pkgname}: Package name. 21 | \item \code{pkgver}: Package version. 22 | } 23 | } 24 | \description{ 25 | Returns information about the current package in a list which can be passed to other functions. 26 | } 27 | \examples{ 28 | \dontrun{ 29 | # Specify path to example package source and copy to tempdir() 30 | # Note that in practice you do not need to copy to a tempdir() 31 | # And in fact it may be easiest to use pracpac relative to your package directory root 32 | ex_pkg_src <- system.file("hellow", package = "pracpac", mustWork = TRUE) 33 | file.copy(from = ex_pkg_src, to = tempdir(), recursive = TRUE) 34 | 35 | # This will succeed if this is a package 36 | pkg_info(pkg_path = file.path(tempdir(), "hellow")) 37 | # This will fail if this is not a package location 38 | pkg_info(pkg_path = tempdir()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /man/pkg_root.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{pkg_root} 4 | \alias{pkg_root} 5 | \title{Find package root} 6 | \usage{ 7 | pkg_root(pkg_path = ".", ...) 8 | } 9 | \arguments{ 10 | \item{pkg_path}{Path to the package directory. Default is \code{"."} for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed.} 11 | 12 | \item{...}{Arguments passed to \link[rprojroot:find_root_file]{rprojroot::find_package_root_file}.} 13 | } 14 | \value{ 15 | A file path of the package root. If no package is found at the root then the function will \code{stop} with an error message. 16 | } 17 | \description{ 18 | Unexported helper to find the root of the R package. Returns an error if the path specified is not an R package. 19 | } 20 | -------------------------------------------------------------------------------- /man/pracpac-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pracpac-package.R 3 | \docType{package} 4 | \name{pracpac-package} 5 | \alias{pracpac} 6 | \alias{pracpac-package} 7 | \title{pracpac: Practical 'R' Packaging in 'Docker'} 8 | \description{ 9 | \if{html}{\figure{logo.png}{options: style='float: right' alt='logo' width='120'}} 10 | 11 | Streamline the creation of 'Docker' images with 'R' packages and dependencies embedded. The 'pracpac' package provides a 'usethis'-like interface to creating Dockerfiles with dependencies managed by 'renv'. The 'pracpac' functionality is described in Nagraj and Turner (2023) \doi{10.48550/arXiv.2303.07876}. 12 | } 13 | \seealso{ 14 | Useful links: 15 | \itemize{ 16 | \item \url{https://signaturescience.github.io/pracpac/} 17 | \item \url{https://github.com/signaturescience/pracpac/} 18 | \item Report bugs at \url{https://github.com/signaturescience/pracpac/issues} 19 | } 20 | 21 | } 22 | \author{ 23 | \strong{Maintainer}: VP Nagraj \email{nagraj@nagraj.net} (\href{https://orcid.org/0000-0003-0060-566X}{ORCID}) 24 | 25 | Authors: 26 | \itemize{ 27 | \item Stephen Turner (\href{https://orcid.org/0000-0001-9140-9028}{ORCID}) 28 | } 29 | 30 | Other contributors: 31 | \itemize{ 32 | \item Signature Science, LLC. [copyright holder] 33 | } 34 | 35 | } 36 | \keyword{internal} 37 | -------------------------------------------------------------------------------- /man/renv_deps.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/docker.R 3 | \name{renv_deps} 4 | \alias{renv_deps} 5 | \title{Get dependencies using renv} 6 | \usage{ 7 | renv_deps( 8 | pkg_path = ".", 9 | img_path = NULL, 10 | other_packages = NULL, 11 | overwrite = TRUE, 12 | consent_renv = TRUE 13 | ) 14 | } 15 | \arguments{ 16 | \item{pkg_path}{Path to the package directory. Default is \code{"."} for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed.} 17 | 18 | \item{img_path}{Path to the write the docker image definition contents. The default \code{NULL} will use \verb{docker/} as a subdirectory of the \code{pkg_path}.} 19 | 20 | \item{other_packages}{Vector of other packages to be included in \code{renv} lock file; default is \code{NULL}.} 21 | 22 | \item{overwrite}{Logical; should an existing lock file should be overwritten? Default is \code{TRUE}.} 23 | 24 | \item{consent_renv}{Logical; give renv consent in this session with \code{options(renv.consent = TRUE)}? Default is \code{TRUE}. See \link[renv:consent]{renv::consent} for details.} 25 | } 26 | \value{ 27 | Invisibly returns a list of package info returned by \link{pkg_info}. Primarily called for side effect. Writes an \code{renv} lock file to the docker/ directory. 28 | } 29 | \description{ 30 | Get dependencies using renv. This function will inspect your package specified 31 | at \code{pkg_path} (default is current working directory, \code{.}), and create an renv lock file (\code{renv.lock}) in 32 | the \verb{docker/} directory. More information about the \code{renv} implementation is provided in the Details section. 33 | } 34 | \details{ 35 | The \code{renv.lock} file will capture all your package's dependencies (and all 36 | their dependencies) at the current version installed on your system at the 37 | time this function is run. When using the default \code{use_renv=TRUE} in 38 | \link{use_docker} or \link{add_dockerfile}, the resulting \code{Dockerfile} will install 39 | packages from this \code{renv.lock} file using \link[renv:restore]{renv::restore}. This ensures that 40 | versions of dependencies in the image mirror what is installed on your system 41 | at the time of image creation, rather than potentially newer versions on package repositories like 42 | CRAN or Bioconductor, which may come with breaking changes that you are unaware of at the 43 | time of package development. 44 | 45 | If there are additional R packages that may be useful for the Docker image you plan to build (but may not be captured under your package dependencies), then you can add these packages to the \code{renv} procedure with the "other_packages" argument. 46 | 47 | This function is run as part of \link{use_docker} but can be used on its own. 48 | } 49 | \examples{ 50 | \dontrun{ 51 | # Specify path to example package source and copy to tempdir() 52 | # Note that in practice you do not need to copy to a tempdir() 53 | # And in fact it may be easiest to use pracpac relative to your package directory root 54 | ex_pkg_src <- system.file("hellow", package = "pracpac", mustWork = TRUE) 55 | file.copy(from = ex_pkg_src, to = tempdir(), recursive = TRUE) 56 | 57 | # Run using defaults; only gets current package dependencies 58 | renv_deps(pkg_path = file.path(tempdir(), "hellow")) 59 | # Add additional packages not explicitly required by your package 60 | renv_deps(pkg_path = file.path(tempdir(), "hellow"), other_packages=c("shiny", "knitr")) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /man/use_docker.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/docker.R 3 | \name{use_docker} 4 | \alias{use_docker} 5 | \title{Use docker packaging tools} 6 | \usage{ 7 | use_docker( 8 | pkg_path = ".", 9 | img_path = NULL, 10 | use_renv = TRUE, 11 | use_case = "default", 12 | base_image = NULL, 13 | other_packages = NULL, 14 | build = FALSE, 15 | repos = NULL, 16 | overwrite_assets = TRUE, 17 | overwrite_renv = TRUE, 18 | consent_renv = TRUE 19 | ) 20 | } 21 | \arguments{ 22 | \item{pkg_path}{Path to the package directory. Default is \code{"."} for the current working directory, which assumes developer is working in R package root. However, this can be set to another path as needed.} 23 | 24 | \item{img_path}{Path to the write the docker image definition contents. The default \code{NULL} will use \verb{docker/} as a subdirectory of the \code{pkg_path}.} 25 | 26 | \item{use_renv}{Logical; use renv? Defaults to \code{TRUE}. If \code{FALSE}, package dependencies are scraped from the \code{DESCRIPTION} file without version information.} 27 | 28 | \item{use_case}{Name of the use case. Defaults to \code{"default"}, which only uses the base boilerplate.} 29 | 30 | \item{base_image}{Name of the base image to start \code{FROM}. Default is \code{NULL} and the base image will be derived based on \code{use_case}. Optionally override this by setting the name of the base image (including tag if desired).} 31 | 32 | \item{other_packages}{Vector of other packages to be included in \code{renv} lock file; default is \code{NULL}.} 33 | 34 | \item{build}{Logical as to whether or not the image should be built. Default is \code{TRUE}, and if \code{FALSE} the \verb{docker build} command will be messaged. Setting \code{build=FALSE} could be useful if additional \verb{docker build} options or different tags are desired. In either case the \verb{docker build} command will be returned invisibly.} 35 | 36 | \item{repos}{Option to override the repos used for installing packages with \code{renv} by passing name of repository. Only used if \code{use_renv = TRUE}. Default is \code{NULL} meaning that the repos specified in \code{renv} lockfile will remain as-is and not be overridden.} 37 | 38 | \item{overwrite_assets}{Logical; should existing asset files should be overwritten? Default is \code{TRUE}.} 39 | 40 | \item{overwrite_renv}{Logical; should an existing lock file should be overwritten? Default is \code{TRUE}; ignored if \code{use_renv = TRUE}.} 41 | 42 | \item{consent_renv}{Logical; give renv consent in this session with \code{options(renv.consent = TRUE)}? Default is \code{TRUE}. See \link[renv:consent]{renv::consent} for details.} 43 | } 44 | \value{ 45 | Invisibly returns a list with information about the package (\verb{$info}) and 46 | the \verb{docker build} command (\verb{$buildcmd}). Primarily called for side effect. 47 | Creates \verb{docker/} directory, identifies renv dependencies and creates lock 48 | file (if \code{use_renv = TRUE}), writes Dockerfile, builds package tar.gz, 49 | moves all relevant assets to the \verb{docker/} directory, and builds Docker 50 | image (if \code{build = TRUE}). 51 | } 52 | \description{ 53 | Wrapper function around other \code{pracpac} functions. See help for the functions linked below for detail on individual functions. 54 | All arguments to \code{use_docker()} are passed to downstream functions. \code{use_docker()} will sequentially run: 55 | \enumerate{ 56 | \item \link{pkg_info} to get information about the current R package. 57 | \item \link{create_docker_dir} to create the \verb{docker/} directory in the specified location, if it doesn't already exist. 58 | \item \link{renv_deps} (if \code{use_renv=TRUE}, the default) to capture package dependencies with renv and create an \code{renv.lock} file 59 | \item \link{add_dockerfile} to create a Dockerfile using template specified by \code{use_case} 60 | \item \link{add_assets} depending on the \code{use_case} 61 | \item \link{build_pkg} to build the current R package source .tar.gz, and place it into the \verb{docker/} directory 62 | \item \link{build_image} optional, default \code{FALSE}; if TRUE, will build the Docker image. 63 | } 64 | 65 | The default \code{build=FALSE} means that everything up to \code{build_image()} is run, 66 | but the image is not actually built. Instead, \code{use_docker()} will message the 67 | \verb{docker build} command, and return that string in \verb{$buildcmd} in the 68 | invisibly returned output. 69 | 70 | See \code{vignette("use-cases", package="pracpac")} for details on use cases. 71 | } 72 | \examples{ 73 | \dontrun{ 74 | 75 | # Specify path to example package source and copy to tempdir() 76 | # Note that in practice you do not need to copy to a tempdir() 77 | # And in fact it may be easiest to use pracpac relative to your package directory root 78 | ex_pkg_src <- system.file("hellow", package = "pracpac", mustWork = TRUE) 79 | file.copy(from = ex_pkg_src, to = tempdir(), recursive = TRUE) 80 | 81 | # Run use_docker to create Docker directory and assets for the example package 82 | use_docker(pkg_path = file.path(tempdir(), "hellow")) 83 | # To not use renv 84 | use_docker(pkg_path = file.path(tempdir(), "hellow"), use_renv=FALSE) 85 | # To specify a use case 86 | use_docker(pkg_path = file.path(tempdir(), "hellow"), use_case="pipeline") 87 | # To overwrite the default base image 88 | use_docker(pkg_path = file.path(tempdir(), "hellow"), base_image="alpine:latest") 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /pracpac.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | LineEndingConversion: Posix 18 | 19 | BuildType: Package 20 | PackageUseDevtools: Yes 21 | PackageInstallArgs: --no-multiarch --with-keep.source 22 | PackageRoxygenize: rd,collate,namespace,vignette 23 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(pracpac) 3 | library(withr) 4 | library(fs) 5 | test_check("pracpac") 6 | -------------------------------------------------------------------------------- /tests/testthat/test_files.R: -------------------------------------------------------------------------------- 1 | library(fs) 2 | library(withr) 3 | tmp <- tempdir() 4 | 5 | ## NOTE: as of v0.2.0 none of the tests use renv snapshot per CRAN req 6 | 7 | test_that("use_docker pipeline creates expected directories and files with defaults", { 8 | 9 | skip_on_cran() 10 | 11 | ex_pkg <- system.file("hellow", package = "pracpac") 12 | dir_create(path = path(tmp, "test"), recurse = TRUE) 13 | dir_copy(ex_pkg, path(tmp, "test")) 14 | with_tempdir({ 15 | use_docker(pkg_path = path(tmp, "test", "hellow"), use_renv = FALSE) 16 | }, 17 | tmpdir = tmp) 18 | 19 | expect_true(dir_exists(path(tmp, "test", "hellow", "docker"))) 20 | expect_true(file_exists(path(tmp, "test", "hellow", "docker", "Dockerfile"))) 21 | expect_true(file_exists(path(tmp, "test", "hellow", "docker", "hellow_0.1.0.tar.gz"))) 22 | 23 | }) 24 | 25 | test_that("build_image returns build command as expected", { 26 | 27 | skip_on_cran() 28 | 29 | # Check build command 30 | buildcmd <- suppressMessages(build_image(pkg_path = path(tmp, "test", "hellow"), build=FALSE)) 31 | expect_identical(as.character(buildcmd), paste("docker build --tag hellow:latest --tag hellow:0.1.0", path(tmp, "test", "hellow", "docker"))) 32 | 33 | ## clean up 34 | ## NOTE: this clean up is not performed in the test prior to this ... 35 | ## lets us retain the files to check build command 36 | ## but now we can cleanup 37 | dir_delete(path(tmp, "test")) 38 | }) 39 | 40 | 41 | test_that("Option to ignore renv works", { 42 | 43 | skip_on_cran() 44 | 45 | ex_pkg <- system.file("hellow", package = "pracpac") 46 | dir_create(path = path(tmp, "test"), recurse = TRUE) 47 | dir_copy(ex_pkg, path(tmp, "test")) 48 | with_tempdir({ 49 | use_docker(pkg_path = path(tmp, "test", "hellow"), use_renv = FALSE) 50 | }, 51 | tmpdir = tmp) 52 | 53 | expect_false(file_exists(path(tmp, "test", "hellow", "docker", "renv.lock"))) 54 | 55 | ## clean up 56 | dir_delete(path(tmp, "test")) 57 | }) 58 | 59 | test_that("Alternative directory structure works", { 60 | 61 | skip_on_cran() 62 | 63 | ex_pkg <- system.file("hellow", package = "pracpac") 64 | dir_create(path = path(tmp, "test"), recurse = TRUE) 65 | dir_copy(ex_pkg, path(tmp, "test")) 66 | with_tempdir({ 67 | ## NOTE: setting use_renv to FALSE to make this test run quicker 68 | use_docker(pkg_path = path(tmp, "test", "hellow"), img_path = path(tmp, "test"), use_renv = FALSE) 69 | }, 70 | tmpdir = tmp) 71 | 72 | expect_true(file_exists(path(tmp, "test", "Dockerfile"))) 73 | expect_false(file_exists(path(tmp, "test", "renv.lock"))) 74 | expect_true(file_exists(path(tmp, "test", "hellow_0.1.0.tar.gz"))) 75 | 76 | }) 77 | 78 | test_that("build_image returns build command as expected with alternative directory", { 79 | 80 | skip_on_cran() 81 | 82 | # Check build command 83 | buildcmd <- suppressMessages(build_image(pkg_path = path(tmp, "test", "hellow"), img_path = path(tmp, "test"), build=FALSE)) 84 | expect_identical(as.character(buildcmd), paste("docker build --tag hellow:latest --tag hellow:0.1.0", path(tmp, "test"))) 85 | 86 | ## clean up 87 | ## NOTE: this clean up is not performed in the test prior to this ... 88 | ## lets us retain the files to check build command 89 | ## but now we can cleanup 90 | dir_delete(path(tmp, "test")) 91 | }) 92 | 93 | test_that("The base image override option works", { 94 | 95 | skip_on_cran() 96 | 97 | ex_pkg <- system.file("hellow", package = "pracpac") 98 | dir_create(path = path(tmp, "test"), recurse = TRUE) 99 | dir_copy(ex_pkg, path(tmp, "test")) 100 | with_tempdir({ 101 | ## NOTE: setting use_renv to FALSE to make this test run quicker 102 | use_docker(pkg_path = path(tmp, "test", "hellow"), use_renv = FALSE, base_image = "debian:10") 103 | }, 104 | tmpdir = tmp) 105 | 106 | ## check the base image 107 | dockerfile <- readLines(path(tmp, "test", "hellow", "docker", "Dockerfile")) 108 | expect_equal(dockerfile[1], "FROM debian:10") 109 | 110 | ## clean up 111 | dir_delete(path(tmp, "test")) 112 | }) 113 | 114 | test_that("Use case templates work (shiny)", { 115 | 116 | skip_on_cran() 117 | 118 | ex_pkg <- system.file("hellow", package = "pracpac") 119 | dir_create(path = path(tmp, "test"), recurse = TRUE) 120 | dir_copy(ex_pkg, path(tmp, "test")) 121 | with_tempdir({ 122 | ## NOTE: setting use_renv to FALSE to make this test run quicker 123 | use_docker(pkg_path = path(tmp, "test", "hellow"), use_renv = FALSE, use_case = "shiny") 124 | }, 125 | tmpdir = tmp) 126 | 127 | expect_true(file_exists(path(tmp, "test", "hellow", "docker", "assets", "app.R"))) 128 | 129 | ## check the base image 130 | dockerfile <- readLines(path(tmp, "test", "hellow", "docker", "Dockerfile")) 131 | expect_equal(dockerfile[1], "FROM rocker/shiny:latest") 132 | 133 | ## clean up 134 | dir_delete(path(tmp, "test")) 135 | }) 136 | 137 | test_that("Use case templates work (rstudio)", { 138 | 139 | skip_on_cran() 140 | 141 | ex_pkg <- system.file("hellow", package = "pracpac") 142 | dir_create(path = path(tmp, "test"), recurse = TRUE) 143 | dir_copy(ex_pkg, path(tmp, "test")) 144 | with_tempdir({ 145 | ## NOTE: setting use_renv to FALSE to make this test run quicker 146 | use_docker(pkg_path = path(tmp, "test", "hellow"), use_renv = FALSE, use_case = "rstudio") 147 | }, 148 | tmpdir = tmp) 149 | 150 | ## check the base image 151 | dockerfile <- readLines(path(tmp, "test", "hellow", "docker", "Dockerfile")) 152 | expect_equal(dockerfile[1], "FROM rocker/rstudio:latest") 153 | 154 | ## clean up 155 | dir_delete(path(tmp, "test")) 156 | }) 157 | 158 | test_that("Use case templates work (pipeline)", { 159 | 160 | skip_on_cran() 161 | 162 | ex_pkg <- system.file("hellow", package = "pracpac") 163 | dir_create(path = path(tmp, "test"), recurse = TRUE) 164 | dir_copy(ex_pkg, path(tmp, "test")) 165 | with_tempdir({ 166 | ## NOTE: setting use_renv to FALSE to make this test run quicker 167 | use_docker(pkg_path = path(tmp, "test", "hellow"), use_renv = FALSE, use_case = "pipeline") 168 | }, 169 | tmpdir = tmp) 170 | 171 | expect_true(file_exists(path(tmp, "test", "hellow", "docker", "assets", "run.sh"))) 172 | expect_true(file_exists(path(tmp, "test", "hellow", "docker", "assets", "pre.R"))) 173 | expect_true(file_exists(path(tmp, "test", "hellow", "docker", "assets", "post.R"))) 174 | 175 | ## check the base image 176 | dockerfile <- readLines(path(tmp, "test", "hellow", "docker", "Dockerfile")) 177 | expect_equal(dockerfile[1], "FROM rocker/r-ver:latest") 178 | 179 | ## clean up 180 | dir_delete(path(tmp, "test")) 181 | }) 182 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /vignettes/basic-usage.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Basic Usage" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{basic-usage} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | warning = FALSE, 14 | message = FALSE, 15 | comment = "#>" 16 | ) 17 | options(rmarkdown.html_vignette.check_title = FALSE) 18 | ``` 19 | 20 | ## Overview 21 | 22 | R packages are the fundamental units of reproducible code in R.[^rpkgs] Docker is a virtualization technology that can be used to bundle an application and all its dependencies in a virtual container that can be distributed and deployed to run reproducibly on any Windows, Linux or MacOS operating system. When used in tandem, these tools can help developers deliver software with an inherently reproducible set of dependencies, including specific dependent R packages. An R package developer may consider building a docker image that contains their R package. This approach can be useful in various scenarios, one of which is a case where the R package includes functions to pre- and post-process data that is also processed by a domain-specific tool written in another language (i.e., something that couldn't be included as an R package dependency). 23 | 24 | We developed `pracpac` with a goal of providing intuitive functions for developers to use custom R packages and Docker together. The `pracpac` package is conceptually inspired by packages like `devtools` and `usethis`, which dramatically reduce the technical burden of R package development. With `pracpac`, users can easily create templates of the necessary files and directory structure to build a Docker image that contains their R package and specific dependency packages, with versions optionally frozen via `renv`. 25 | 26 | [^rpkgs]: Hadley Wickham and Jenny Bryan. _R Packages (2e)_. . 27 | 28 | ## Terminology 29 | 30 | It may be useful to clarify Docker terminology used throughout: 31 | 32 | - **Image**: Snapshot built to define *container* contents 33 | - **Container**: Instantiated *image* that can be run on the host 34 | 35 | For more information on Docker installation, terminology, and usage see [https://docs.docker.com/](https://docs.docker.com/). 36 | 37 | ## Using `pracpac` 38 | 39 | The `pracpac` package is designed to do two things: 40 | 41 | 1. Template files/directories for a Docker image containing an R package 42 | 2. Build the Docker image 43 | 44 | These features are delivered in the `use_docker()` and `build_image()` functions. 45 | 46 | ### `use_docker()` 47 | 48 | The `pracpac` package includes individual functions to add a template Dockerfile, build the source of an R package to be added to the Docker image, and define dependencies for that package in an `renv` lock file. All files created are moved to the Docker directory specified by the user, which as a default is set to a `docker/` subdirectory of the R package. For convenience, the `pracpac` functionality is wrapped into the `use_docker()` function. 49 | 50 | The example that follows uses the `hellow` R package that ships with `pracpac`. With `pracpac` installed, the `hellow` source code can be found with the following command: 51 | 52 | ```{r, eval=FALSE} 53 | system.file("hellow", package = "pracpac") 54 | ``` 55 | 56 | To motivate the basic usage we will demonstrate how to use the example package copied to a `tempdir()`. 57 | 58 | **NOTE**: In practice, it is likely more convenient to use `pracpac` functions within the flow of R package development (i.e., with the working directory at the package root). As such, the file copying here may not be necessary for most usage. 59 | 60 | ```{r, eval=FALSE} 61 | library(pracpac) 62 | library(fs) 63 | 64 | ## specify the temp directory 65 | tmp <- tempdir() 66 | ## create a subdirectory of temp called "example" 67 | dir_create(path = path(tmp, "example")) 68 | ## copy the example hellow package to the temp directory 69 | dir_copy(path = system.file("hellow", package = "pracpac"), new_path = path(tmp, "example")) 70 | ``` 71 | 72 | The contents of the `hellow` package source are structured as follows: 73 | 74 | ``` 75 | ├── DESCRIPTION 76 | ├── LICENSE 77 | ├── LICENSE.md 78 | ├── NAMESPACE 79 | ├── R 80 | │ └── hello.R 81 | ├── hellow.Rproj 82 | └── man 83 | └── isay.Rd 84 | ``` 85 | 86 | To create the template for a Docker image that contains the `hellow` R package the developer can use `use_docker()`: 87 | 88 | ```{r, eval=FALSE} 89 | use_docker(pkg_path = path(tmp, "example", "hellow")) 90 | ``` 91 | 92 | 93 | ``` 94 | ├── DESCRIPTION 95 | ├── LICENSE 96 | ├── LICENSE.md 97 | ├── NAMESPACE 98 | ├── R 99 | │ └── hello.R 100 | ├── docker 101 | │ ├── Dockerfile 102 | │ ├── hellow_0.1.0.tar.gz 103 | │ └── renv.lock 104 | ├── hellow.Rproj 105 | └── man 106 | └── isay.Rd 107 | ``` 108 | 109 | With defaults set, this function will create a `Dockerfile` with the following contents: 110 | 111 | ```Dockerfile 112 | FROM rocker/r-ver:latest 113 | 114 | ## copy the renv.lock into the image 115 | COPY renv.lock /renv.lock 116 | 117 | ## install renv 118 | RUN Rscript -e 'install.packages(c("renv"))' 119 | 120 | ## set the renv path var to the renv lib 121 | ENV RENV_PATHS_LIBRARY renv/library 122 | 123 | ## restore packages from renv.lock 124 | RUN Rscript -e 'renv::restore(lockfile = "/renv.lock", repos = NULL)' 125 | 126 | ## copy in built R package 127 | COPY hellow_0.1.0.tar.gz /hellow_0.1.0.tar.gz 128 | 129 | ## run script to install built R package from source 130 | RUN Rscript -e 'install.packages("/hellow_0.1.0.tar.gz", type='source', repos=NULL)' 131 | ``` 132 | 133 | And an `renv.lock` with the dependencies of `hellow` (in this case just the `praise` package): 134 | 135 | ```json 136 | { 137 | "R": { 138 | "Version": "4.0.2", 139 | "Repositories": [ 140 | { 141 | "Name": "CRAN", 142 | "URL": "https://cran.rstudio.com" 143 | } 144 | ] 145 | }, 146 | "Packages": { 147 | "praise": { 148 | "Package": "praise", 149 | "Version": "1.0.0", 150 | "Source": "Repository", 151 | "Repository": "CRAN", 152 | "Hash": "a555924add98c99d2f411e37e7d25e9f", 153 | "Requirements": [] 154 | } 155 | } 156 | } 157 | ``` 158 | 159 | The `use_docker()` defaults will produce the behavior described above. However, the functionality can be customized further. For example, the user can optionally specify a use case to create variants of template files (described in more detail in other vignettes). Another option is to specify an `img_path` defining where the files used to build the Docker image should be written, which may be useful for developers who prefer not to build images within the R package root. The following shows how this could be used to write the Docker template files to the directory above the package root: 160 | 161 | ```{r, eval = FALSE} 162 | use_docker(pkg_path = path(tmp, "example", "hellow"), img_path = path(tmp, "example")) 163 | ``` 164 | 165 | ``` 166 | ├── Dockerfile 167 | ├── hellow 168 | │ ├── DESCRIPTION 169 | │ ├── LICENSE 170 | │ ├── LICENSE.md 171 | │ ├── NAMESPACE 172 | │ ├── R 173 | │ │ └── hello.R 174 | │ ├── hellow.Rproj 175 | │ └── man 176 | │ └── isay.Rd 177 | ├── hellow_0.1.0.tar.gz 178 | ``` 179 | 180 | For a full list of options see `?use_docker`. 181 | 182 | ### `build_image()` 183 | 184 | The `use_docker()` function includes an option to "build". By default this parameter is set to `FALSE`. The `pracpac` templates are likely to require some editing by the developer. However, after editing the `Dockerfile` and any constituent files to be added the user can call `build_image()` to build the Docker image: 185 | 186 | ```{r, eval=FALSE} 187 | build_image(pkg_path = path(tmp, "example", "hellow")) 188 | ``` 189 | 190 | Note that if the user has specified a different `img_path` in `use_docker()`, then the same path needs to be used with `build_image()`. 191 | 192 | By default the image will be built and tagged with the name of the R package and a "latest" and version suffix: 193 | 194 | ```{r, eval=FALSE} 195 | system("docker images") 196 | ``` 197 | 198 | ``` 199 | hellow 0.1.0 e1a9bc2ebbb5 15 seconds ago 828MB 200 | hellow latest e1a9bc2ebbb5 15 seconds ago 828MB 201 | ``` 202 | 203 | The tagging scheme can be altered with the "tag" argument. The `build_image()` function also includes a parameter to leverage the Docker build "cache" feature. For more details see `?build_image`. To use additional build parameters the user can call the Docker daemon directly on the host or use a client like [`stevedore`](https://CRAN.R-project.org/package=stevedore). 204 | -------------------------------------------------------------------------------- /vignettes/use-cases.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Use Cases" 3 | output: rmarkdown::html_vignette 4 | vignette: > 5 | %\VignetteIndexEntry{use-cases} 6 | %\VignetteEngine{knitr::rmarkdown} 7 | %\VignetteEncoding{UTF-8} 8 | --- 9 | 10 | ```{r, include = FALSE} 11 | knitr::opts_chunk$set( 12 | collapse = TRUE, 13 | warning = FALSE, 14 | message = FALSE, 15 | comment = "" 16 | ) 17 | options(rmarkdown.html_vignette.check_title = FALSE) 18 | ``` 19 | 20 | ## Overview 21 | 22 | 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 image[^terminology] may prove useful in other scenarios as well. 23 | 24 | [^terminology]: Note that we discuss Docker terminology in the "Basic Usage" vignette: `vignette("basic-usage", package = "pracpac")` 25 | 26 | ## Pipeline 27 | 28 | 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. 29 | 30 | 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`[^ts] 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. 31 | 32 | [^ts]: https://github.com/soimort/translate-shell 33 | 34 | ### Setting up the Docker template 35 | 36 | We will first move the example `hellow` package to a temporary location: 37 | 38 | ```{r, eval=FALSE} 39 | library(pracpac) 40 | library(fs) 41 | 42 | ## specify the temp directory 43 | tmp <- tempdir() 44 | ## copy the example hellow package to the temp directory 45 | dir_copy(path = system.file("hellow", package = "pracpac"), new_path = path(tmp, "example", "hellow")) 46 | ``` 47 | 48 | The new directory includes the R package source contents of `hellow`: 49 | 50 | 51 | 52 | 53 | ```{r, eval=FALSE} 54 | dir_tree(path(tmp, "example", "hellow"), recurse = TRUE) 55 | ``` 56 | 57 | ``` 58 | ├── DESCRIPTION 59 | ├── NAMESPACE 60 | ├── R 61 | │ └── hello.R 62 | └── man 63 | └── isay.Rd 64 | ``` 65 | 66 | We can use `use_docker(..., use_case="pipeline")` to create the template of files for building the Docker image: 67 | 68 | 69 | 70 | 71 | ```{r, results='hide', eval=FALSE} 72 | use_docker(pkg_path = path(tmp, "example", "hellow"), use_case = "pipeline") 73 | ``` 74 | 75 | ``` 76 | Using renv. Dockerfile will build from renv.lock in /tmp/RtmpsMexB6/example/hellow/docker. 77 | Using template for the specified use case: pipeline 78 | Writing dockerfile: /tmp/RtmpsMexB6/example/hellow/docker/Dockerfile 79 | The directory will be created at /tmp/RtmpsMexB6/example/hellow/docker/assets 80 | Assets for the specified use case (pipeline) will be copied there. 81 | The specified use case (pipeline) includes the following asset: run.sh 82 | The specified use case (pipeline) includes the following asset: pre.R 83 | The specified use case (pipeline) includes the following asset: post.R 84 | Building package hellow version 0.1.0 in /tmp/RtmpsMexB6/example/hellow/hellow_0.1.0.tar.gz 85 | docker build command: 86 | docker build --tag hellow:latest --tag hellow:0.1.0 /tmp/RtmpsMexB6/example/hellow/docker 87 | ``` 88 | 89 | The directory now contains the `docker/` subdirectory, which has another subdirectory called `assets/` for the templated pipeline scripts: 90 | 91 | 92 | 93 | 94 | ```{r, eval=FALSE} 95 | dir_tree(path(tmp, "example", "hellow"), recurse = TRUE) 96 | ``` 97 | 98 | ``` 99 | ├── DESCRIPTION 100 | ├── NAMESPACE 101 | ├── R 102 | │ └── hello.R 103 | ├── docker 104 | │ ├── Dockerfile 105 | │ ├── assets 106 | │ │ ├── post.R 107 | │ │ ├── pre.R 108 | │ │ └── run.sh 109 | │ ├── hellow_0.1.0.tar.gz 110 | │ └── renv.lock 111 | └── man 112 | └── isay.Rd 113 | ``` 114 | 115 | The files need to be edited as follows: 116 | 117 | ```{r, eval=FALSE, echo=FALSE} 118 | # actually make those changes! don't echo and don't eval. because you're 119 | # creating the file at tmp from the template, which includes post.R, you can't 120 | # just run this code interactively because post.R won't exist, the actual 121 | # dockerfile at tmp remains unchanged so copy in the "final" dockerfile and 122 | # assets into the tmp dir so the docker build works properly below when running 123 | # interactively, without having to actually monkey with the files in tmp, and so 124 | # after the container is built it actually has the assets, entrypoint, etc. 125 | file_copy(system.file("example/hellow/Dockerfile", package = "pracpac"), path(tmp, "example/hellow/docker/Dockerfile"), overwrite=TRUE) 126 | file_copy(system.file("example/hellow/pre.R", package = "pracpac"), path(tmp, "example/hellow/docker/assets/pre.R"), overwrite=TRUE) 127 | file_copy(system.file("example/hellow/run.sh", package = "pracpac"), path(tmp, "example/hellow/docker/assets/run.sh"), overwrite=TRUE) 128 | ``` 129 | 130 | #### `Dockerfile` 131 | 132 | ```{bash eval=FALSE, echo=TRUE, code=readLines(system.file("example", "hellow", "Dockerfile", package = "pracpac"))} 133 | ``` 134 | 135 | #### `run.sh` 136 | 137 | ```{bash eval=FALSE, echo=TRUE, code=readLines(system.file("example", "hellow", "run.sh", package = "pracpac"))} 138 | ``` 139 | 140 | #### `pre.R` 141 | 142 | ```{r eval=FALSE, echo=TRUE, code=readLines(system.file("example", "hellow", "pre.R", package = "pracpac"))} 143 | ``` 144 | 145 | Note that in this case the `docker/assets/post.R` template is not necessary, so we can delete it: 146 | 147 | ```{r, eval=FALSE} 148 | file_delete(path(tmp, "example", "hellow", "docker", "assets", "post.R")) 149 | ``` 150 | 151 | ### Building the image 152 | 153 | With the template files created and edited as described above, we can build the image: 154 | 155 | ```{r, eval=FALSE} 156 | build_image(pkg_path = path(tmp, "example", "hellow")) 157 | ``` 158 | 159 | In this case, the image will be built with `build_image()` default behavior of tagging with the package name and version: 160 | 161 | ```{r, eval=FALSE} 162 | system("docker images") 163 | ``` 164 | 165 | ``` 166 | hellow 0.1.0 e1a9bc2ebbb5 15 seconds ago 959MB 167 | hellow latest e1a9bc2ebbb5 15 seconds ago 959MB 168 | ``` 169 | 170 | ### Running the container 171 | 172 | Now we can run the container, either from a Docker client directly on the host or from within R: 173 | 174 | ```{r, eval = FALSE} 175 | system("docker run --rm hellow:latest") 176 | ``` 177 | 178 | ``` 179 | You are peachy! 180 | [1] "Hello" 181 | 182 | [1] "Bonjour" 183 | 184 | Translations of [1] "Hello" 185 | [ English -> Français ] 186 | 187 | [1] "Hello" 188 | [1] "Bonjour", [1] "Salut" 189 | ``` 190 | 191 | ```{r, eval = FALSE} 192 | system("docker run --rm hellow:latest :es") 193 | ``` 194 | 195 | ``` 196 | You are groundbreaking! 197 | [1] "What's up?" 198 | 199 | [1] "¿Qué pasa?" 200 | 201 | Translations of [1] "What's up?" 202 | [ English -> Español ] 203 | 204 | [1] "What's up?" 205 | [1] "¿Qué pasa?", [1] "¿Qué hay de nuevo?" 206 | ``` 207 | 208 | ```{r, echo=FALSE, eval=FALSE} 209 | ## cleanup needed in case on vignette rebuild the same tmp directory is picked 210 | dir_delete(path = path(tmp, "example")) 211 | ``` 212 | 213 | 214 | ## Shiny 215 | 216 | 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. 217 | 218 | 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. 219 | 220 | ### Setting up the Docker template 221 | 222 | We will first move the example `ocf` package to a temporary location: 223 | 224 | ```{r, eval=FALSE} 225 | library(pracpac) 226 | library(fs) 227 | 228 | ## specify the temp directory 229 | tmp <- tempdir() 230 | ## copy the example ocf package to the temp directory 231 | dir_copy(path = system.file("ocf", package = "pracpac"), new_path = path(tmp, "example", "ocf")) 232 | ``` 233 | 234 | ```{r, eval=FALSE} 235 | dir_tree(path(tmp, "example", "ocf"), recurse = TRUE) 236 | ``` 237 | ``` 238 | ├── DESCRIPTION 239 | ├── NAMESPACE 240 | ├── R 241 | │ └── pal.R 242 | ├── inst 243 | │ └── app 244 | │ └── app.R 245 | └── man 246 | └── get_pal.Rd 247 | ``` 248 | 249 | 250 | We can use `use_docker(..., use_case="shiny")` to create the template of files for building the Docker image: 251 | 252 | 253 | 254 | 255 | ```{r, results='hide', eval=FALSE} 256 | use_docker(pkg_path = path(tmp, "example", "ocf"), use_case = "shiny") 257 | ``` 258 | 259 | ``` 260 | Using renv. Dockerfile will build from renv.lock in /tmp/RtmpsMexB6/example/ocf/docker. 261 | Using template for the specified use case: shiny 262 | Writing dockerfile: /tmp/RtmpsMexB6/example/ocf/docker/Dockerfile 263 | The directory will be created at /tmp/RtmpsMexB6/example/ocf/docker/assets 264 | Assets for the specified use case (shiny) will be copied there. 265 | The specified use case (shiny) includes the following asset: app.R 266 | Building package ocf version 0.1.0 in /tmp/RtmpsMexB6/example/ocf/ocf_0.1.0.tar.gz 267 | docker build command: 268 | docker build --tag ocf:latest --tag ocf:0.1.0 /tmp/RtmpsMexB6/example/ocf/docker 269 | ``` 270 | 271 | The directory now contains the `docker/` subdirectory, which has another subdirectory called `assets/` for the templated Shiny files: 272 | 273 | 274 | 275 | 276 | ```{r, eval=FALSE} 277 | dir_tree(path(tmp, "example", "ocf"), recurse = TRUE) 278 | ``` 279 | 280 | ``` 281 | ├── DESCRIPTION 282 | ├── NAMESPACE 283 | ├── R 284 | │ └── pal.R 285 | ├── docker 286 | │ ├── Dockerfile 287 | │ ├── assets 288 | │ │ └── app.R 289 | │ ├── ocf_0.1.0.tar.gz 290 | │ └── renv.lock 291 | ├── inst 292 | │ └── app 293 | │ └── app.R 294 | └── man 295 | └── get_pal.Rd 296 | ``` 297 | 298 | One of the files needs to be edited as follows: 299 | 300 | ```{r, eval=FALSE, echo=FALSE} 301 | # actually make those changes! don't echo and don't eval. need to edit app.R 302 | file_copy(system.file("example/ocf/app.R", package = "pracpac"), path(tmp, "example/ocf/docker/assets/app.R"), overwrite=TRUE) 303 | ``` 304 | 305 | #### `app.R` 306 | 307 | ```{r eval=FALSE, echo=TRUE, code=readLines(system.file("example", "ocf", "app.R", package = "pracpac"))} 308 | ``` 309 | 310 | 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). 311 | 312 | ### Building the image 313 | 314 | With the template files created and edited as described above, we can build the image: 315 | 316 | ```{r, eval=FALSE} 317 | build_image(pkg_path = path(tmp, "example", "ocf")) 318 | ``` 319 | 320 | In this case, the image will be built with `build_image()` default behavior of tagging with the package name and version: 321 | 322 | ```{r, eval=FALSE} 323 | system("docker images") 324 | ``` 325 | 326 | ### Running the container 327 | 328 | Now we can run the container, either from a Docker client directly on the host or from within R: 329 | 330 | ```{r, eval = FALSE} 331 | system("docker run --rm -it -d --user shiny -p 3838:3838 ocf:0.1.0") 332 | ``` 333 | 334 | 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. 335 | 336 | ```{r, echo=FALSE, eval=FALSE} 337 | ## cleanup needed in case on vignette rebuild the same tmp directory is picked 338 | dir_delete(path = path(tmp, "example")) 339 | ``` 340 | --------------------------------------------------------------------------------