diff --git a/NAMESPACE b/NAMESPACE index 2cd288b3..e0773c28 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -19,6 +19,7 @@ export(endpoint_serializer) export(forward) export(getCharacterSet) export(get_character_set) +export(get_option_or_env) export(include_file) export(include_html) export(include_md) diff --git a/NEWS.md b/NEWS.md index 28803a94..de7074af 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,6 @@ # plumber (development version) +* Allow to set plumber options using environment variables `?options_plumber`. (@meztez #934) * Add support for quoted boundary for multipart request parsing. (@meztez #924) * Fix #916, related to `parseUTF8` return value attribute `srcfile` on Windows. (#930) diff --git a/R/options_plumber.R b/R/options_plumber.R index 94f4ff5a..a5f9d6be 100644 --- a/R/options_plumber.R +++ b/R/options_plumber.R @@ -2,7 +2,9 @@ #' #' There are a number of global options that affect Plumber's behavior. These can #' be set globally with [options()] or with [options_plumber()]. Options set using -#' [options_plumber()] should not include the `plumber.` prefix. +#' [options_plumber()] should not include the `plumber.` prefix. Alternatively, +#' environment variable can be used to set plumber options using uppercase and +#' underscores (i.e. to set `plumber.apiHost` you can set environment variable `PLUMBER_APIHOST`). #' #' \describe{ #' \item{`plumber.port`}{Port Plumber will attempt to use to start http server. @@ -89,3 +91,23 @@ options_plumber <- function( plumber.legacyRedirects = legacyRedirects ) } + +#' Get an option value, alternatively look in environment for value +#' @rdname options_plumber +#' @inheritParams base::options +#' @export +get_option_or_env <- function(x, default = NULL) { + + getOption(x, default = { + env_name <- toupper(chartr(".", "_", x)) + res <- Sys.getenv(env_name) + if (res == "") { + return(default) + } + if (res %in% c("TRUE", "FALSE")) { + return(as.logical(res)) + } + res + }) + +} diff --git a/R/plumber.R b/R/plumber.R index 03cefd73..9050b46a 100644 --- a/R/plumber.R +++ b/R/plumber.R @@ -159,7 +159,7 @@ Plumber <- R6Class( #' @importFrom rlang missing_arg run = function( host = '127.0.0.1', - port = getOption('plumber.port', NULL), + port = get_option_or_env('plumber.port', NULL), swagger = deprecated(), debug = missing_arg(), swaggerCallback = missing_arg(), @@ -211,7 +211,7 @@ Plumber <- R6Class( port <- findPort(port) # Delay setting max size option. It could be set in `plumber.R`, which is after initialization - private$maxSize <- getOption('plumber.maxRequestSize', 0) #0 Unlimited + private$maxSize <- get_option_or_env('plumber.maxRequestSize', 0) #0 Unlimited # Delay the setting of swaggerCallback as long as possible. # An option could be set in `plumber.R`, which is after initialization @@ -219,7 +219,7 @@ Plumber <- R6Class( swaggerCallback <- rlang::maybe_missing(swaggerCallback, rlang::maybe_missing(private$docs_callback, - getOption('plumber.docs.callback', getOption('plumber.swagger.url', NULL)) + get_option_or_env('plumber.docs.callback', get_option_or_env('plumber.swagger.url', NULL)) ) ) @@ -784,7 +784,7 @@ Plumber <- R6Class( # No endpoint could handle this request. 404 notFoundStep <- function(...) { - if (isTRUE(getOption("plumber.trailingSlash", FALSE))) { + if (isTRUE(get_option_or_env("plumber.trailingSlash", FALSE))) { # Redirect to the slash route, if it exists path <- req$PATH_INFO # If the path does not end in a slash, @@ -813,7 +813,7 @@ Plumber <- R6Class( # No trailing-slash route exists... # Try allowed verbs - if (isTRUE(getOption("plumber.methodNotAllowed", TRUE))) { + if (isTRUE(get_option_or_env("plumber.methodNotAllowed", TRUE))) { # Notify about allowed verbs if (is_405(req$pr, req$PATH_INFO, req$REQUEST_METHOD)) { res$status <- 405L @@ -933,7 +933,7 @@ Plumber <- R6Class( #' If using [options_plumber()], the value must be set before initializing your Plumber router. #' @param ... Arguments for the visual documentation. See each visual documentation package for further details. setDocs = function( - docs = getOption("plumber.docs", TRUE), + docs = get_option_or_env("plumber.docs", TRUE), ... ) { private$docs_info <- upgrade_docs_parameter(docs, ...) @@ -948,7 +948,7 @@ Plumber <- R6Class( #' See also: [pr_set_docs_callback()] #' @param callback a callback function for taking action on the docs url. (Also accepts `NULL` values to disable the `callback`.) setDocsCallback = function( - callback = getOption('plumber.docs.callback', NULL) + callback = get_option_or_env('plumber.docs.callback', NULL) ) { # Use callback when defined if (!length(callback) || !is.function(callback)) { diff --git a/R/pr.R b/R/pr.R index 40f1b4be..b9f302c9 100644 --- a/R/pr.R +++ b/R/pr.R @@ -520,7 +520,7 @@ pr_filter <- function(pr, #' @export pr_run <- function(pr, host = '127.0.0.1', - port = getOption('plumber.port', NULL), + port = get_option_or_env('plumber.port', NULL), ..., debug = missing_arg(), docs = missing_arg(), diff --git a/R/pr_set.R b/R/pr_set.R index dd66561f..a62c8158 100644 --- a/R/pr_set.R +++ b/R/pr_set.R @@ -177,7 +177,7 @@ pr_set_debug <- function(pr, debug = interactive()) { #' } pr_set_docs <- function( pr, - docs = getOption("plumber.docs", TRUE), + docs = get_option_or_env("plumber.docs", TRUE), ... ) { validate_pr(pr) @@ -206,7 +206,7 @@ pr_set_docs <- function( #' } pr_set_docs_callback <- function( pr, - callback = getOption('plumber.docs.callback', NULL) + callback = get_option_or_env('plumber.docs.callback', NULL) ) { validate_pr(pr) pr$setDocsCallback(callback = callback) diff --git a/R/shared-secret-filter.R b/R/shared-secret-filter.R index 0e508f2b..9e3f72ee 100644 --- a/R/shared-secret-filter.R +++ b/R/shared-secret-filter.R @@ -1,6 +1,6 @@ #' @noRd sharedSecretFilter <- function(req, res){ - secret <- getOption("plumber.sharedSecret", NULL) + secret <- get_option_or_env("plumber.sharedSecret", NULL) if (!is.null(secret)){ supplied <- req$HTTP_PLUMBER_SHARED_SECRET if (!identical(supplied, secret)){ @@ -9,7 +9,7 @@ sharedSecretFilter <- function(req, res){ res$serializer <- serializer_unboxed_json() # Using output similar to `defaultErrorHandler()` li <- list(error = "400 - Bad request") - + # Don't overly leak data unless they opt-in if (is.function(req$pr$getDebug) && isTRUE(req$pr$getDebug())) { li$message <- "Shared secret mismatch" diff --git a/R/ui.R b/R/ui.R index 65a562d8..18e6c2b9 100644 --- a/R/ui.R +++ b/R/ui.R @@ -10,13 +10,13 @@ mount_docs <- function(pr, host, port, docs_info, callback, quiet = FALSE) { } # Build api url - api_url <- getOption( + api_url <- get_option_or_env( "plumber.apiURL", urlHost( - scheme = getOption("plumber.apiScheme", "http"), - host = getOption("plumber.apiHost", host), - port = getOption("plumber.apiPort", port), - path = getOption("plumber.apiPath", ""), + scheme = get_option_or_env("plumber.apiScheme", "http"), + host = get_option_or_env("plumber.apiHost", host), + port = get_option_or_env("plumber.apiPort", port), + path = get_option_or_env("plumber.apiPath", ""), changeHostLocation = TRUE ) ) @@ -91,8 +91,8 @@ mount_openapi <- function(pr, api_url) { openapi_fun <- function(req) { # use the HTTP_REFERER so RSC can find the Docs location to ask ## (can't directly ask for 127.0.0.1) - if (is.null(getOption("plumber.apiURL")) && - is.null(getOption("plumber.apiHost"))) { + if (is.null(get_option_or_env("plumber.apiURL")) && + is.null(get_option_or_env("plumber.apiHost"))) { if (is.null(req$HTTP_REFERER)) { # Prevent leaking host and port if option is not set api_url <- character(1) @@ -256,7 +256,7 @@ registered_docs <- function() { swagger_redirects <- function() { - if (!isTRUE(getOption("plumber.legacyRedirects", TRUE))) { + if (!isTRUE(get_option_or_env("plumber.legacyRedirects", TRUE))) { return(list()) } diff --git a/man/Plumber.Rd b/man/Plumber.Rd index 576d47c8..5090da29 100644 --- a/man/Plumber.Rd +++ b/man/Plumber.Rd @@ -217,7 +217,7 @@ See also: \code{\link[=pr_run]{pr_run()}} \subsection{Usage}{ \if{html}{\out{