diff --git a/NEWS.md b/NEWS.md index 112a3cb6..c654c306 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,15 @@ * Ignore `.env`, `.venv`, and `venv` files only when they reference Python virtual environments. (#972) + +* Add `envManagement`, `envManagementR` and `envManagementPy` args when writing + the `manifest.json`. These args whether Connect should install packages in the + package cache. If `envManagement` is `FALSE` then Connect will not perform any + package installation and it is the administrators responsibility to ensure the + required R/Python packages are available in the runtime environment. + This is especially useful if off-host execution is enabled, when the execution + environment (specified by `--image`) already contains the required packages. + Requires Posit Connect `>=2023.07.0`. (#977) # rsconnect 1.0.2 diff --git a/R/bundle.R b/R/bundle.R index b902a09b..de415a4b 100644 --- a/R/bundle.R +++ b/R/bundle.R @@ -109,6 +109,9 @@ createAppManifest <- function(appDir, pythonConfig = NULL, retainPackratDirectory = TRUE, image = NULL, + envManagement = NULL, + envManagementR = NULL, + envManagementPy = NULL, verbose = FALSE, quiet = FALSE) { @@ -191,9 +194,36 @@ createAppManifest <- function(appDir, # add metadata manifest$metadata <- metadata - # if there is a target image, attach it to the environment - if (!is.null(image)) { - manifest$environment <- list(image = image) + # handle shorthand arg to enable/disable both R and Python + if (!is.null(envManagement)) { + envManagementR <- envManagement + envManagementPy <- envManagement + } + + # if envManagement is explicitly enabled/disabled, + # create an environment_management obj + envManagementInfo <- list() + if (!is.null(envManagementR)) { + envManagementInfo$r <- envManagementR + } + if (!is.null(envManagementPy)) { + envManagementInfo$python <- envManagementPy + } + + # emit the environment field + if (!is.null(image) || length(envManagementInfo) > 0) { + manifest$environment <- list() + + # if there is a target image, attach it to the environment + if (!is.null(image)) { + manifest$environment$image <- image + } + + # if either environment_management.r or environment_management.python + # is provided, write the environment_management field + if (length(envManagementInfo) > 0) { + manifest$environment$environment_management <- envManagementInfo + } } # indicate whether this is a quarto app/doc diff --git a/R/deployApp.R b/R/deployApp.R index a7401d5b..4c98bf91 100644 --- a/R/deployApp.R +++ b/R/deployApp.R @@ -142,6 +142,24 @@ #' @param image Optional. The name of the image to use when building and #' executing this content. If none is provided, Posit Connect will #' attempt to choose an image based on the content requirements. +#' @param envManagement Optional. Should Connect install R and Python packages +#' for this content? (`TRUE`, `FALSE`, or `NULL`). The default, `NULL`, will +#' not write any values to the bundle manifest, and Connect will fall back to +#' the application default environment management strategy, or the server +#' default if no application default is defined. +#' +#' (This option is a shorthand flag which overwrites the values of both +#' `envManagementR` and `envManagementPy`.) +#' @param envManagementR Optional. Should Connect install R packages +#' for this content? (`TRUE`, `FALSE`, or `NULL`). The default, `NULL`, will +#' not write any values to the bundle manifest, and Connect will fall back to +#' the application default R environment management strategy, or the server +#' default if no application default is defined. +#' @param envManagementPy Optional. Should Connect install Python packages +#' for this content? (`TRUE`, `FALSE`, or `NULL`). The default, `NULL`, will +#' not write any values to the bundle manifest, and Connect will fall back to +#' the application default Python environment management strategy, or the server +#' default if no application default is defined. #' @examples #' \dontrun{ #' @@ -197,7 +215,10 @@ deployApp <- function(appDir = getwd(), forceGeneratePythonEnvironment = FALSE, quarto = NA, appVisibility = NULL, - image = NULL + image = NULL, + envManagement = NULL, + envManagementR = NULL, + envManagementPy = NULL ) { check_string(appDir) @@ -424,7 +445,10 @@ deployApp <- function(appDir = getwd(), quiet = quiet, verbose = verbose, pythonConfig = pythonConfig, - image = image + image = image, + envManagement = envManagement, + envManagementR = envManagementR, + envManagementPy = envManagementPy ) size <- format(file_size(bundlePath), big.mark = ",") taskComplete(quiet, "Created {size}b bundle") @@ -589,7 +613,10 @@ bundleApp <- function(appName, verbose = FALSE, quiet = FALSE, pythonConfig = NULL, - image = NULL) { + image = NULL, + envManagement = NULL, + envManagementR = NULL, + envManagementPy = NULL) { logger <- verboseLogger(verbose) # get application users (for non-document deployments) @@ -615,6 +642,9 @@ bundleApp <- function(appName, pythonConfig = pythonConfig, retainPackratDirectory = TRUE, image = image, + envManagement = envManagement, + envManagementR = envManagementR, + envManagementPy = envManagementPy, verbose = verbose, quiet = quiet ) diff --git a/R/writeManifest.R b/R/writeManifest.R index c8398a64..904ec600 100644 --- a/R/writeManifest.R +++ b/R/writeManifest.R @@ -26,6 +26,9 @@ writeManifest <- function(appDir = getwd(), forceGeneratePythonEnvironment = FALSE, quarto = NA, image = NULL, + envManagement = NULL, + envManagementR = NULL, + envManagementPy = NULL, verbose = FALSE, quiet = FALSE) { appFiles <- listDeploymentFiles( @@ -61,6 +64,9 @@ writeManifest <- function(appDir = getwd(), pythonConfig = pythonConfig, retainPackratDirectory = FALSE, image = image, + envManagement = envManagement, + envManagementR = envManagementR, + envManagementPy = envManagementPy, verbose = verbose, quiet = quiet ) diff --git a/man/deployApp.Rd b/man/deployApp.Rd index 4044d44f..edec3f2b 100644 --- a/man/deployApp.Rd +++ b/man/deployApp.Rd @@ -30,7 +30,10 @@ deployApp( forceGeneratePythonEnvironment = FALSE, quarto = NA, appVisibility = NULL, - image = NULL + image = NULL, + envManagement = NULL, + envManagementR = NULL, + envManagementPy = NULL ) } \arguments{ @@ -177,6 +180,27 @@ made. Currently has an effect only on deployments to shinyapps.io.} \item{image}{Optional. The name of the image to use when building and executing this content. If none is provided, Posit Connect will attempt to choose an image based on the content requirements.} + +\item{envManagement}{Optional. Should Connect install R and Python packages +for this content? (\code{TRUE}, \code{FALSE}, or \code{NULL}). The default, \code{NULL}, will +not write any values to the bundle manifest, and Connect will fall back to +the application default environment management strategy, or the server +default if no application default is defined. + +(This option is a shorthand flag which overwrites the values of both +\code{envManagementR} and \code{envManagementPy}.)} + +\item{envManagementR}{Optional. Should Connect install R packages +for this content? (\code{TRUE}, \code{FALSE}, or \code{NULL}). The default, \code{NULL}, will +not write any values to the bundle manifest, and Connect will fall back to +the application default R environment management strategy, or the server +default if no application default is defined.} + +\item{envManagementPy}{Optional. Should Connect install Python packages +for this content? (\code{TRUE}, \code{FALSE}, or \code{NULL}). The default, \code{NULL}, will +not write any values to the bundle manifest, and Connect will fall back to +the application default Python environment management strategy, or the server +default if no application default is defined.} } \description{ Deploy a \link[shiny:shiny-package]{shiny} application, an diff --git a/man/writeManifest.Rd b/man/writeManifest.Rd index 011cca5e..8f4d9a8b 100644 --- a/man/writeManifest.Rd +++ b/man/writeManifest.Rd @@ -15,6 +15,9 @@ writeManifest( forceGeneratePythonEnvironment = FALSE, quarto = NA, image = NULL, + envManagement = NULL, + envManagementR = NULL, + envManagementPy = NULL, verbose = FALSE, quiet = FALSE ) @@ -69,6 +72,27 @@ there are \code{.qmd} files in the bundle, or if there is a executing this content. If none is provided, Posit Connect will attempt to choose an image based on the content requirements.} +\item{envManagement}{Optional. Should Connect install R and Python packages +for this content? (\code{TRUE}, \code{FALSE}, or \code{NULL}). The default, \code{NULL}, will +not write any values to the bundle manifest, and Connect will fall back to +the application default environment management strategy, or the server +default if no application default is defined. + +(This option is a shorthand flag which overwrites the values of both +\code{envManagementR} and \code{envManagementPy}.)} + +\item{envManagementR}{Optional. Should Connect install R packages +for this content? (\code{TRUE}, \code{FALSE}, or \code{NULL}). The default, \code{NULL}, will +not write any values to the bundle manifest, and Connect will fall back to +the application default R environment management strategy, or the server +default if no application default is defined.} + +\item{envManagementPy}{Optional. Should Connect install Python packages +for this content? (\code{TRUE}, \code{FALSE}, or \code{NULL}). The default, \code{NULL}, will +not write any values to the bundle manifest, and Connect will fall back to +the application default Python environment management strategy, or the server +default if no application default is defined.} + \item{verbose}{If \code{TRUE}, prints detailed progress messages.} \item{quiet}{If \code{FALSE}, prints progress messages.} diff --git a/tests/testthat/test-writeManifest.R b/tests/testthat/test-writeManifest.R index 683db0b4..959eb557 100644 --- a/tests/testthat/test-writeManifest.R +++ b/tests/testthat/test-writeManifest.R @@ -225,6 +225,29 @@ test_that("Sets environment.image in the manifest if one is provided", { expect_null(manifest$environment) }) +test_that("Sets environment.environment_management in the manifest if envManagement is defined", { + withr::local_options(renv.verbose = TRUE) + + appDir <- test_path("shinyapp-simple") + + # test shorthand arg + manifest <- makeManifest(appDir, envManagement = FALSE, envManagementR = TRUE, envManagementPy = TRUE) + expect_equal(manifest$environment$environment_management$r, FALSE) + expect_equal(manifest$environment$environment_management$python, FALSE) + + # test R and Python args + manifest <- makeManifest(appDir, envManagementR = TRUE) + expect_equal(manifest$environment$environment_management$r, TRUE) + expect_null(manifest$environment$environment_management$python) + manifest <- makeManifest(appDir, envManagementPy = TRUE) + expect_equal(manifest$environment$environment_management$python, TRUE) + expect_null(manifest$environment$environment_management$r) + + # environment_management is not defined when envManagementR and envManagementPy are NULL + manifest <- makeManifest(appDir, image = "rstudio/content-base:latest") + expect_null(manifest$environment$environment_management) +}) + # appMode Inference tests test_that("content type (appMode) is inferred and can be overridden", {