Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
^README\.Rmd$
^\.github$
^\.gitlab-ci\.yml$
^tests/testthat/fixtures$
Comment thread
VincentGuyader marked this conversation as resolved.
7 changes: 4 additions & 3 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ Imports:
yesno
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.2
RoxygenNote: 7.3.3
URL: https://github.com/VincentGuyader/shiny2docker
BugReports: https://github.com/VincentGuyader/shiny2docker/issues
Suggests:
Suggests:
shiny,
renv,
testthat (>= 3.0.0),
knitr,
rmarkdown,
rstudioapi
rstudioapi,
withr
Config/testthat/edition: 3
VignetteBuilder: knitr
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# Generated by roxygen2: do not edit by hand

export(install_uvr)
export(set_github_action)
export(set_gitlab_ci)
export(shiny2docker)
export(shiny2docker_uvr)
importFrom(attachment,create_renv_for_prod)
importFrom(cli,cli_alert_danger)
importFrom(cli,cli_alert_info)
importFrom(cli,cli_alert_success)
importFrom(dockerfiler,Dockerfile)
importFrom(dockerfiler,dock_from_renv)
importFrom(here,here)
importFrom(yesno,yesno2)
140 changes: 140 additions & 0 deletions R/shiny2docker_uvr.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#' shiny2docker_uvr
#'
#' Generate a Dockerfile for a Shiny application using
#' [uvr](https://github.com/nbafrank/uvr) instead of `renv`. The R version is
#' installed inside the container by `uvr` (no `rocker/r-ver` base image
#' required) and packages are restored from `uvr.lock` via `uvr sync`.
#'
#' Lifecycle: experimental. The function expects (or bootstraps) the three
#' uvr files — `uvr.toml`, `uvr.lock`, and `.r-version` — at the root of
#' `path`, under those exact filenames (the generated Dockerfile copies them
#' by name from the build context, matching uvr's own convention).
#'
#' Bootstrapping (default `bootstrap = TRUE`): if any of the three files is
#' missing, `shiny2docker_uvr()` creates them automatically — the same way
#' `shiny2docker()` auto-creates `renv.lock` via
#' `attachment::create_renv_for_prod()`. Concretely, when `uvr.toml` is
#' missing, we generate a `renv.lock` (via `attachment`) if needed, then run
#' `uvr init` + `uvr import`. When `.r-version` is missing, we pin to the
#' current local R version. When `uvr.lock` is missing or stale, we run
#' `uvr lock`. This requires the `uvr` CLI on `PATH`; if absent, you get an
#' actionable error with install instructions. Set `bootstrap = FALSE` to
#' fail fast instead.
#'
#' What `uvr_assert_state()` checks after bootstrapping: the three files exist,
#' `uvr.lock` is at least as recent as `uvr.toml` (mtime check), and
#' `.r-version` contains a single `MAJOR.MINOR.PATCH` line. It does **not**
#' parse `uvr.toml` to cross-validate the pinned R version against any
#' `[r] version` constraint — uvr itself owns that consistency contract.
#'
#' Platform constraints: the generated Dockerfile uses `apt-get` and Debian
#' package names, and downloads the `x86_64-unknown-linux-gnu` uvr binary, so
#' `base_image` must be a Debian/Ubuntu-derived image on amd64/glibc.
#'
#' @param path Character. Path to the folder containing the Shiny application
#' *and* the uvr files (`uvr.toml`, `uvr.lock`, `.r-version`).
#' @param output Character. Path to the generated Dockerfile.
#' @param base_image Character. Docker base image. Defaults to `"debian:stable-slim"`.
#' Must be a Debian/Ubuntu-based amd64 image (see "Platform constraints" above).
#' @param uvr_version Character. `"latest"` or a semver tag like `"v0.3.1"`.
#' Pinning a tag is recommended for reproducible builds.
#' @param port Integer in `[1, 65535]`. Shiny port to expose. Default `3838`.
#' @param host Character. Shiny host. Default `"0.0.0.0"`. Single value, no
#' shell metacharacters; validated before being interpolated into the image.
#' @param extra_sysreqs Character vector of extra debian package names to
#' install before `uvr sync` (escape hatch for system dependencies that uvr
#' does not detect on its own). Each entry must match
#' `[A-Za-z0-9.+:-]+` to avoid shell injection.
#' @param bootstrap Logical. If `TRUE` (default), missing `uvr.toml` /
#' `uvr.lock` / `.r-version` are auto-generated by calling the local `uvr`
#' CLI (and `attachment::create_renv_for_prod()` for the dep list). When
#' `FALSE`, missing files trigger an immediate error, matching the strict
#' behaviour expected in CI. Requires the `uvr` binary on `PATH` only when
#' actual bootstrapping is needed.
#' @param frozen Logical. If `TRUE`, the generated Dockerfile runs
#' `uvr sync --frozen` so the build fails when `uvr.lock` is out of date
#' relative to `uvr.toml`. Default `FALSE` because uvr 0.2.15 rejects
#' locks generated on a different host than the container's target OS;
#' opt in once your setup supports it. When `FALSE`, a `cli` warning is
#' emitted to make the relaxed strictness explicit.
#' @param write Logical. Whether to write the Dockerfile and `.dockerignore`
#' to disk. When `FALSE`, the function only returns the in-memory Dockerfile
#' object and does not touch the filesystem. Default `TRUE`. The parent
#' directory of `output` is created if it does not already exist.
#'
#' @return Invisibly, an R6 `dockerfiler::Dockerfile` object that can be further
#' customised before writing.
#'
#' @export
#'
#' @importFrom dockerfiler Dockerfile
shiny2docker_uvr <- function(path = ".",
output = file.path(path, "Dockerfile"),
base_image = "debian:stable-slim",
uvr_version = "latest",
port = 3838,
host = "0.0.0.0",
extra_sysreqs = NULL,
bootstrap = TRUE,
frozen = FALSE,
write = TRUE) {

uvr_toml <- file.path(path, "uvr.toml")
uvr_lock <- file.path(path, "uvr.lock")
rver_file <- file.path(path, ".r-version")

needs_bootstrap <- !file.exists(uvr_toml) ||
!file.exists(uvr_lock) ||
!file.exists(rver_file)

if (needs_bootstrap) {
if (!isTRUE(bootstrap)) {
stop(
"uvr files missing at '", path,
"/'. Re-run with `bootstrap = TRUE`, or run `uvr init`/`uvr import`/`uvr lock` manually.",
call. = FALSE
)
}
uvr_bootstrap(path = path)
}

uvr_assert_state(
uvr_toml = uvr_toml,
uvr_lock = uvr_lock,
r_version_file = rver_file
)

uvr_validate_host_port(host = host, port = port)
uvr_validate_sysreqs(extra_sysreqs)

if (!isTRUE(frozen)) {
cli::cli_alert_warning(paste(
"frozen = FALSE: the generated build runs `uvr sync` (no --frozen),",
"so a stale uvr.lock will not block the image. Set frozen = TRUE",
"once your environment supports it."
))
}

dock <- uvr_build_dockerfile(
base_image = base_image,
uvr_version = uvr_version,
port = port,
host = host,
extra_sysreqs = extra_sysreqs,
frozen = frozen
)

if (isTRUE(write)) {
out_dir <- dirname(output)
if (!dir.exists(out_dir)) {
dir.create(out_dir, recursive = TRUE, showWarnings = FALSE)
}
dockerignore <- file.path(out_dir, ".dockerignore")
if (!file.exists(dockerignore)) {
create_dockerignore_uvr(path = dockerignore)
}
dock$write(output)
}

invisible(dock)
}
Loading
Loading