From 836b50199ed8b48dd7dccf479ed4f0fb4238e6d1 Mon Sep 17 00:00:00 2001 From: Maximilian Held Date: Tue, 8 Sep 2020 00:24:35 +0200 Subject: [PATCH] factor out deployment funs closes #49 starting #4 --- .Rbuildignore | 2 + DESCRIPTION | 9 +- NAMESPACE | 10 +- R/azure.R | 491 ++++++++++++------ man-roxygen/azure.R | 2 + man/az_account.Rd | 66 +++ man/az_cli_run.Rd | 92 ++-- man/az_configure.Rd | 60 +++ man/az_login.Rd | 42 ++ man/az_webapp.Rd | 190 +++---- man/az_webapp_show.Rd | 25 - ...clude_app2_azure.Rd => include_app2_az.Rd} | 16 +- man/shiny_deploy_az.Rd | 143 +++++ ..._webapp_shiny_opts.Rd => shiny_opts_az.Rd} | 16 +- pkgdown/_pkgdown.yml | 1 + shinycaas.Rproj | 5 + tests/testthat/setup-azure.R | 28 +- tests/testthat/test-azure.R | 47 +- 18 files changed, 855 insertions(+), 390 deletions(-) create mode 100644 man-roxygen/azure.R create mode 100644 man/az_account.Rd create mode 100644 man/az_configure.Rd create mode 100644 man/az_login.Rd delete mode 100644 man/az_webapp_show.Rd rename man/{include_app2_azure.Rd => include_app2_az.Rd} (74%) create mode 100644 man/shiny_deploy_az.Rd rename man/{az_webapp_shiny_opts.Rd => shiny_opts_az.Rd} (71%) diff --git a/.Rbuildignore b/.Rbuildignore index f296dbb..e48db62 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -10,3 +10,5 @@ ^pkgdown$ Dockerfile ^vignettes/ +^\.azure$ +man-roxygen diff --git a/DESCRIPTION b/DESCRIPTION index 5fab91a..2ebb2ec 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -20,15 +20,16 @@ URL: https://subugoe.github.io/shinycaas, https://github.com/subugoe/shinycaas BugReports: https://github.com/subugoe/shinycaas/issues Suggests: testthat, + withr, shiny (>= 1.5.0), - subugoetheme + subugoetheme, + fs Imports: processx (>= 3.4.3), cli (>= 2.0.2), checkmate (>= 2.0.0), - withr (>= 2.2.0), - fs (>= 1.4.0), - htmltools (>= 0.5.0) + htmltools (>= 0.5.0), + jsonlite (>= 1.7.0) Remotes: subugoe/subugoetheme@1.0.0 SystemRequirements: diff --git a/NAMESPACE b/NAMESPACE index abe0173..fa3bed1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,9 +1,13 @@ # Generated by roxygen2: do not edit by hand +export(az_account) +export(az_cli_run) +export(az_configure) +export(az_login) export(az_webapp) -export(az_webapp_shiny_opts) -export(az_webapp_show) export(include_app2) -export(include_app2_azure) +export(include_app2_az) export(is_github_actions) export(runOldFaithful) +export(shiny_deploy_az) +export(shiny_opts_az) diff --git a/R/azure.R b/R/azure.R index 3e33909..2226d9e 100644 --- a/R/azure.R +++ b/R/azure.R @@ -1,81 +1,210 @@ +# wrap all ==== #' Deploy a shiny app to Azure #' -#' Calls the [Azure Command-Line Interface (CLI)](https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest) with defaults suitable for deploying a shiny app. +#' Calls the with defaults suitable for deploying a shiny app. #' Wraps several steps. #' -#' @param restart whether to restart the web app. -#' -#' @param \dots passed on to other functions or placeholder. +#' @inheritDotParams az_account +#' @inheritDotParams az_configure +#' @inheritDotParams az_webapp +#' @inheritDotParams az_webapp_create +#' @inheritDotParams az_webapp_config_container_set #' #' @example tests/testthat/setup-azure.R #' -#' @family azure functions +#' @template azure #' #' @export -az_webapp <- function(restart = FALSE, - ...) { - checkmate::assert_flag(restart) - az_account_set(...) +shiny_deploy_az <- function(...) { + az_account(...) az_configure(...) - az_webapp_create(...) - az_webapp_deployment_slot_create(...) - az_webapp_config_container_set(...) - az_webapp_update(...) - az_webapp_config_set(...) - az_webapp_config_appsettings_set(...) + az_webapp(...) +} - if (restart) { - az_webapp_restart(...) +# az login ==== +#' Log in to Azure +#' +#' Helper for interactive login. +#' Do not script this function unless you know what you are doing. +#' +#' @template azure +#' +#' @export +az_login <- function(...) { + az_cli_run( + cmd = "login", + ... + ) +} + +# az account ==== +#' Manage Azure subscription information +#' +#' Subscription defaults to `NULL`, in which case the subscription is expected to be enabled in the azure CLI cache already. +#' Appends `subscription`, if provided. +#' Errors out if no subscription is enabled. +#' +#' @inheritDotParams az_cli_run +#' @template azure +#' +#' @export +az_account <- function(subscription = NULL, ...) { + if (!is.null(subscription)) { + az_account_set(subscription = subscription, ...) + } + res <- az_account_list(...) + if (length(res) == 0) { + stop("There are no enabled subscriptions.") } + cli::cli_alert_info( + "Found enabled subscription{?s} named {.field {res$name}}." + ) + res +} + +#' @describeIn az_account Get a list of subscriptions for the logged in account. +az_account_list <- function(...) { + az_cli_run(cmd = c("account", "list"), ...) } -#' @describeIn az_webapp Setting subscription +#' @describeIn az_account Set a subscription to be the current active subscription. +#' +#' @details +#' Subscriptions are kept in the local azure CLI cache, so you should not have to run this more than once. +#' On GitHub Actions, [the azure login action](https://github.com/azure/login) will already set up a subscription. #' #' @param subscription #' Name or ID of the Azure subscription to which costs are billed. #' According to an upvoted answer on Stack Overflow, [Azure subscription IDs need not be considered a secret or personal identifiable information (PII)](https://stackoverflow.com/questions/45661109/are-azure-subscription-id-aad-tenant-id-and-aad-app-client-id-considered-secre). #' However, depending your applicable context and policies, you may want to provide this argument as a secret. +#' +#' To find out which subscriptions you are currently authorised to use, run `print(az_account_list())`. az_account_set <- function(subscription, ...) { checkmate::assert_string(subscription) cli::cli_alert_info("Setting subscription ...") - # unclear where those are set - az_cli_run(args = c("account", "set", "--subscription", subscription)) + az_cli_run( + cmd = c("account", "set"), + req = c("--subscription", subscription), + ... + ) } -#' @describeIn az_webapp -#' Set defaults for az CLI. -#' Does not leave side effects (`.azure/`), but cleans up after itself. -#' Must be called repeatedly. -#' If a folder `.azure` is already in the working directory, those defaults are used and a warning is issued. +# az configure ==== +#' Manage Azure CLI configuration +#' +#' Overwrites defaults for the Azure CLI to `.azure/` directory (**side effect**), if arguments are provided. +#' Errors out if a default is missing. +#' +#' @details +#' Because of an apparent [bug](https://github.com/Azure/azure-cli/issues/15014), the Azure CLI will always include defaults at `~/.azure/`. +#' These hidden defaults can interfere with these functions. +#' Make sure that you have no default `name` and `resource_group` in the global azure default config in your `HOME` directory. #' #' @param name #' Name of the web app. +#' (In the Azure CLI, this argument is sometimes known as `name`, and sometimes as `web`). #' #' @param resource_group #' The [Azure resource group](https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal) to which the shiny app should belong. -az_configure <- function(name, resource_group, ...) { - # precaution against ambiguous credentials and later cleanup deletion - if (fs::dir_exists(".azure")) { - cli::cli_alert_warning( - "Using the defaults found in {.file .azure} at the working directory ..." +#' +#' @inheritDotParams az_cli_run +#' @template azure +#' +#' @export +az_configure <- function(name = NULL, resource_group = NULL, ...) { + is_changed <- FALSE + if (!is.null(name)) { + az_cli_run( + cmd = "configure", + opt = c( + # only applies to current folder, useful if there are various projects + "--scope", "local", + "--defaults", paste0("web=", name) + ), + ... ) - return(NULL) + is_changed <- TRUE + } + if (!is.null(resource_group)) { + az_cli_run( + cmd = "configure", + opt = c( + # only applies to current folder, useful if there are various projects + "--scope", "local", + "--defaults", paste0("group=", resource_group) + ), + ... + ) + is_changed <- TRUE } - checkmate::assert_string(name) - checkmate::assert_string(resource_group) - cli::cli_alert_info("Setting defaults ...") - az_cli_run(args = c( - "configure", - # only applies to current folder, useful if there are various projects - "--scope", "local", - "--defaults", paste0("group=", resource_group), paste0("web=", name) - )) - # clean up not to let defaults linger, gottabe grandparent - withr::defer(fs::dir_delete(".azure"), envir = parent.frame(2)) + if (is_changed) { + cli::cli_alert_info( + "Wrote defaults to {.file .azure/} at the working directory." + ) + } + res <- az_configure_list(...) + if (is.null(res$resource_group)) { + stop("No resource group provided.") + } + # not quite strict/consistent, but it makes life easier of no name is allowed + cli::cli_alert_success( + "Using resource group {res$resource_group} and name {res$name} ..." + ) + res +} + +#' @describeIn az_configure List defaults +az_configure_list <- function(...) { + output <- list( + resource_group = NULL, + name = NULL + ) + res <- az_cli_run( + cmd = "configure", + opt = c( + "--list-defaults", "true", + # only applies to current folder, useful if there are various projects + "--scope", "local" + ), + ... + ) + if (length(res) == 0) { + return(output) + } + if (checkmate::test_string(res[res$name == "group", "value"], min.chars = 1)) { + output$resource_group <- res[res$name == "group", "value"] + } + if (checkmate::test_string(res[res$name == "web", "value"], min.chars = 1)) { + output$name <- res[res$name == "web", "value"] + } + output +} + +# az_webapp ==== +#' Manage web apps +#' +#' @param restart whether to restart the web app. +#' +#' @inheritParams az_account_set +#' @inheritDotParams az_cli_run +#' +#' @template azure +#' @export +az_webapp <- function(slot = NULL, restart = TRUE, + ...) { + checkmate::assert_flag(restart) + az_webapp_create(...) + if (!is.null(slot)) az_webapp_deployment_slot_create(slot = slot, ...) + az_webapp_config_container_set(slot = slot, ...) + az_webapp_update(slot = slot, ...) + az_webapp_config_set(slot = slot, ...) + az_webapp_config_appsettings_set(slot = slot, ...) + if (restart) az_webapp_restart(slot = slot, ...) } -#' @describeIn az_webapp -#' Creates a web app +#' @describeIn az_webapp Create a web app +#' +#' @inheritParams az_configure #' #' @param plan #' Name or resource id of the app service plan. @@ -93,46 +222,90 @@ az_configure <- function(name, resource_group, ...) { #' The following `startup-file`s are *invalid*: #' - `"Rscript -e 1 + 1"` (spaces inside `[EXPR]`) #' - `"Rscript -e '1+1'"` (quoting of `[EXPR]` would be treated as `"Rscript -e '\"1+1\"'"`). -az_webapp_create <- function(plan, - name, - startup_file = NULL, +az_webapp_create <- function(name = NULL, + plan, + resource_group = NULL, deployment_container_image_name, + startup_file = NULL, + subscription = NULL, ...) { + checkmate::assert_string(name, null.ok = TRUE) + checkmate::assert_string(plan, null.ok = FALSE) + checkmate::assert_string(resource_group, null.ok = TRUE) + checkmate::assert_string(deployment_container_image_name, null.ok = FALSE) checkmate::assert_string(startup_file, null.ok = TRUE) cli::cli_alert_info("Creating or updating web app ...") - az_cli_run(args = c( - "webapp", "create", - "--name", name, - "--plan", plan, - # az webapp create, though undocumented, requires either an image name or a runtime - # other container settings are set below - "--deployment-container-image-name", deployment_container_image_name, - if (!is.null(startup_file)) { - c("--startup-file", startup_file) - } - # todo also pass on tags #25 - )) + az_cli_run( + cmd = c("webapp", "create"), + req = c( + if (!is.null(name)) c("--name", name), + "--plan", plan, + if (!is.null(resource_group)) c("--resource-group", resource_group) + ), + opt = c( + # az webapp create, though undocumented, requires either an image name or a runtime + # other container settings are set below + "--deployment-container-image-name", deployment_container_image_name, + if (!is.null(startup_file)) c("--startup-file", startup_file), + if (!is.null(subscription)) c("--subscription", subscription) + # todo also pass on tags #25 + ), + ... + ) +} + +#' @describeIn az_webapp Delete a web app +az_webapp_delete <- function(name = NULL, slot = NULL, ...) { + az_cli_run( + cmd = c("webapp", "delete"), + opt = c( + if (!is.null(name)) c("--name", name), + if (!is.null(slot)) c("--slot", slot) + ), + ... + ) } -#' @describeIn az_webapp Creates a deployment slot +#' @describeIn az_webapp List web apps +az_webapp_list <- function(...) { + az_cli_run(cmd = c("webapp", "list"), ...) +} + +#' @describeIn az_webapp Gets the details of a web app +az_webapp_show <- function(slot = NULL, ...) { + az_cli_run( + cmd = c("webapp", "show"), + opt = c( + if (!is.null(slot)) c("--slot", slot) + ), + ... + ) +} + +#' @describeIn az_webapp Create a deployment slot #' #' @param slot #' The name of the [deployment slot](https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots). #' Defaults to the production slot if not specified. #' Only available for higher app service plan tiers. -az_webapp_deployment_slot_create <- function(name, slot = NULL, ...) { - checkmate::assert_string(slot, null.ok = TRUE) - if (!is.null(slot)) { - cli::cli_alert_info("Creating deployment slot ...") - az_cli_run(args = c( - "webapp", "deployment", "slot", "create", - "--name", name, +az_webapp_deployment_slot_create <- function(name = NULL, + resource_group = NULL, + slot, + ...) { + checkmate::assert_string(slot, null.ok = FALSE) + cli::cli_alert_info("Creating deployment slot ...") + az_cli_run( + cmd = c("webapp", "deployment", "slot", "create"), + req = c( + if (!is.null(name)) c("--name", name), + if (!is.null(resource_group)) c("--resource-group", resource_group), "--slot", slot - )) - } + ), + ... + ) } -#' @describeIn az_webapp Sets web app container settings +#' @describeIn az_webapp Set a web app container's settings #' #' @param deployment_container_image_name #' The custom image name and optionally the tag name. @@ -159,94 +332,126 @@ az_webapp_config_container_set <- function(deployment_container_image_name, checkmate::assert_string(docker_registry_server_url, null.ok = TRUE) checkmate::assert_string(docker_registry_server_user, null.ok = TRUE) checkmate::assert_string(docker_registry_server_password, null.ok = TRUE) - cli::cli_alert_info("Setting web app container settings") - az_cli_run(args = c( - "webapp", "config", "container", "set", - # redundant, container is already set above, but safer\ - # otherwise command might be called with no args - "--docker-custom-image-name", deployment_container_image_name, - if (!is.null(docker_registry_server_url)) { - c("--docker-registry-server-url", docker_registry_server_url) - }, - if (!is.null(docker_registry_server_user)) { - c("--docker-registry-server-user", docker_registry_server_user) - }, - if (!is.null(docker_registry_server_password)) { - c("--docker-registry-server-password", docker_registry_server_password) - }, - if (!is.null(slot)) { - c("--slot", slot) - } - )) + cli::cli_alert_info("Setting web app container settings ...") + az_cli_run( + cmd = c("webapp", "config", "container", "set"), + opt = c( + # redundant, container is already set above, but safer\ + # otherwise command might be called with no args + "--docker-custom-image-name", deployment_container_image_name, + if (!is.null(docker_registry_server_url)) { + c("--docker-registry-server-url", docker_registry_server_url) + }, + if (!is.null(docker_registry_server_user)) { + c("--docker-registry-server-user", docker_registry_server_user) + }, + if (!is.null(docker_registry_server_password)) { + c("--docker-registry-server-password", docker_registry_server_password) + }, + if (!is.null(slot)) c("--slot", slot) + ), + ... + ) } -#' @describeIn az_webapp Sets web app tags +#' @describeIn az_webapp Update a web app az_webapp_update <- function(slot = NULL, ...) { cli::cli_alert_info("Setting web app tags ...") # for some reason, this is not part of the webapp config, though it is on portal.azure.com - az_cli_run(args = c( - "webapp", "update", - "--client-affinity-enabled", "true", # send traffic to same machine - "--https-only", "true", - if (!is.null(slot)) { - c("--slot", slot) - } - )) + az_cli_run( + cmd = c("webapp", "update"), + opt = c( + "--client-affinity-enabled", "true", # send traffic to same machine + "--https-only", "true", + if (!is.null(slot)) { + c("--slot", slot) + } + ), + ... + ) } -#' @describeIn az_webapp Sets web app configuration +#' @describeIn az_webapp Set a web app's configuration az_webapp_config_set <- function(slot = NULL, ...) { cli::cli_alert_info("Setting web app configuration ...") - az_cli_run(args = c( - "webapp", "config", "set", - "--always-on", "true", - "--ftps-state", "disabled", # not needed - "--web-sockets-enabled", "true", # needed to serve shiny - "--http20-enabled", "true", - "--min-tls-version", "1.2", - if (!is.null(slot)) { - c("--slot", slot) - } - )) + az_cli_run( + cmd = c("webapp", "config", "set"), + opt = c( + "--always-on", "true", + "--ftps-state", "disabled", # not needed + "--web-sockets-enabled", "true", # needed to serve shiny + "--http20-enabled", "true", + "--min-tls-version", "1.2", + if (!is.null(slot)) c("--slot", slot) + ), + ... + ) } -#' @describeIn az_webapp Sets web app appsetings +#' @describeIn az_webapp Get details of a web app container's settings +az_webapp_config_container_show <- function(slot = NULL, ...) { + az_cli_run( + cmd = c("webapp", "config", "container", "show"), + opt = c(if (!is.null(slot)) c("--slot", slot)), + ... + ) +} + +#' @describeIn az_webapp Set a web app's settings az_webapp_config_appsettings_set <- function(slot = NULL, ...) { # weirdly this cannot be set in the above - az_cli_run(args = c( - "webapp", "config", "appsettings", "set", - "--settings", "DOCKER_ENABLE_CI=false", - if (!is.null(slot)) { - c("--slot", slot) - } - )) + az_cli_run( + cmd = c("webapp", "config", "appsettings", "set"), + opt = c( + "--settings", "DOCKER_ENABLE_CI=false", + if (!is.null(slot)) c("--slot", slot) + ), + ... + ) } #' @describeIn az_webapp Restarts the web app az_webapp_restart <- function(slot = NULL, ...) { cli::cli_alert_info("Restaring web app ...") - az_cli_run(args = c( - "webapp", "restart", - if (!is.null(slot)) { - c("--slot", slot) - } - )) + az_cli_run( + cmd = c("webapp", "restart"), + opt = c(if (!is.null(slot)) c("--slot", slot)), + ... + ) } +# helpers ==== + #' Run Azure CLI -#' -#' @inheritDotParams processx::run -#' -#' @keywords internal -az_cli_run <- function(...) { - processx::run( +#' Wraps the [Azure Command-Line Interface (CLI)](https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest). +#' @param cmd,req,opt,add +#' Command, required, optional, additional parameters, as for [processx::run()] +#' `add` parameters are reserved for the user to pass down additional arguments. +#' @inheritParams processx::run +#' @return (invisible) `stdout` parsed through [jsonlite::fromJSON()] +#' @family azure functions +#' @export +az_cli_run <- function(cmd, + req = NULL, + opt = NULL, + add = NULL, + echo_cmd = FALSE, + echo = FALSE, + ...) { + res <- processx::run( command = "az", - echo_cmd = TRUE, + # redudantly setting json output to be safe; this is expected below + args = c(cmd, req, opt, "--output", "json", add), + echo_cmd = echo_cmd, spinner = TRUE, - echo = TRUE, - ... + echo = echo ) + if (res$stdout == "") { + # some az commands return nothing, so we have to protect against that + return(NULL) + } + invisible(jsonlite::fromJSON(res$stdout)) } #' Shiny options for Azure @@ -257,12 +462,12 @@ az_cli_run <- function(...) { #' If your image suggests `EXPOSE`d ports, that may be respected by Azure (undocumented behavior). #' - `options(shiny.host = "0.0.0.0")` to make your shiny application accessable to the Azure Webapp hosting environment. #' -#' You can also set these options manually as in the below example. +#' You can also set these options manually, see [az_webapp()]. #' #' @family azure functions #' #' @export -az_webapp_shiny_opts <- function() { +shiny_opts_az <- function() { port <- Sys.getenv("PORT") if (port == "") { cli::cli_alert_warning( @@ -282,20 +487,6 @@ az_webapp_shiny_opts <- function() { # withr::defer(options(old_opts), envir = parent.frame(2)) } -#' Get details of a web app -#' -#' @inheritParams az_webapp -#' @inheritDotParams az_webapp -#' -#' @family azure functions -#' -#' @export -az_webapp_show <- function(...) { - res <- az_cli_run(args = c( - "webapp", "show" - )) -} - #' Embed a shiny app hosted on Azure #' #' @inheritDotParams include_app2 @@ -305,6 +496,6 @@ az_webapp_show <- function(...) { #' @family azure functions #' #' @export -include_app2_azure <- function(slot = NULL, ...) { +include_app2_az <- function(slot = NULL, ...) { NULL } diff --git a/man-roxygen/azure.R b/man-roxygen/azure.R new file mode 100644 index 0000000..3415347 --- /dev/null +++ b/man-roxygen/azure.R @@ -0,0 +1,2 @@ +#' @family azure functions +#' @inheritDotParams az_cli_run diff --git a/man/az_account.Rd b/man/az_account.Rd new file mode 100644 index 0000000..6da9d49 --- /dev/null +++ b/man/az_account.Rd @@ -0,0 +1,66 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/azure.R +\name{az_account} +\alias{az_account} +\alias{az_account_list} +\alias{az_account_set} +\title{Manage Azure subscription information} +\usage{ +az_account(subscription = NULL, ...) + +az_account_list(...) + +az_account_set(subscription, ...) +} +\arguments{ +\item{subscription}{Name or ID of the Azure subscription to which costs are billed. +According to an upvoted answer on Stack Overflow, \href{https://stackoverflow.com/questions/45661109/are-azure-subscription-id-aad-tenant-id-and-aad-app-client-id-considered-secre}{Azure subscription IDs need not be considered a secret or personal identifiable information (PII)}. +However, depending your applicable context and policies, you may want to provide this argument as a secret. + +To find out which subscriptions you are currently authorised to use, run \code{print(az_account_list())}.} + +\item{...}{ + Arguments passed on to \code{\link[=az_cli_run]{az_cli_run}}, \code{\link[=az_cli_run]{az_cli_run}} + \describe{ + \item{\code{cmd}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{req}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{opt}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{add}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{echo_cmd}}{Whether to print the command to run to the screen.} + \item{\code{echo}}{Whether to print the standard output and error +to the screen. Note that the order of the standard output and error +lines are not necessarily correct, as standard output is typically +buffered.} + }} +} +\description{ +Subscription defaults to \code{NULL}, in which case the subscription is expected to be enabled in the azure CLI cache already. +Appends \code{subscription}, if provided. +Errors out if no subscription is enabled. +} +\details{ +Subscriptions are kept in the local azure CLI cache, so you should not have to run this more than once. +On GitHub Actions, \href{https://github.com/azure/login}{the azure login action} will already set up a subscription. +} +\section{Functions}{ +\itemize{ +\item \code{az_account_list}: Get a list of subscriptions for the logged in account. + +\item \code{az_account_set}: Set a subscription to be the current active subscription. +}} + +\seealso{ +Other azure functions: +\code{\link{az_cli_run}()}, +\code{\link{az_configure}()}, +\code{\link{az_login}()}, +\code{\link{az_webapp}()}, +\code{\link{include_app2_az}()}, +\code{\link{shiny_deploy_az}()}, +\code{\link{shiny_opts_az}()} +} +\concept{azure functions} diff --git a/man/az_cli_run.Rd b/man/az_cli_run.Rd index 561dd2e..f35a1a7 100644 --- a/man/az_cli_run.Rd +++ b/man/az_cli_run.Rd @@ -2,66 +2,52 @@ % Please edit documentation in R/azure.R \name{az_cli_run} \alias{az_cli_run} -\title{Run Azure CLI} +\title{Run Azure CLI +Wraps the \href{https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest}{Azure Command-Line Interface (CLI)}.} \usage{ -az_cli_run(...) +az_cli_run( + cmd, + req = NULL, + opt = NULL, + add = NULL, + echo_cmd = FALSE, + echo = FALSE, + ... +) } \arguments{ -\item{...}{ - Arguments passed on to \code{\link[processx:run]{processx::run}} - \describe{ - \item{\code{command}}{Character scalar, the command to run.} - \item{\code{args}}{Character vector, arguments to the command.} - \item{\code{error_on_status}}{Whether to throw an error if the command returns -with a non-zero status, or it is interrupted. The error classes are -\code{system_command_status_error} and \code{system_command_timeout_error}, -respectively, and both errors have class \code{system_command_error} as -well. See also "Error conditions" below.} - \item{\code{wd}}{Working directory of the process. If \code{NULL}, the current -working directory is used.} - \item{\code{echo_cmd}}{Whether to print the command to run to the screen.} - \item{\code{echo}}{Whether to print the standard output and error +\item{cmd, req, opt, add}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + +\item{echo_cmd}{Whether to print the command to run to the screen.} + +\item{echo}{Whether to print the standard output and error to the screen. Note that the order of the standard output and error lines are not necessarily correct, as standard output is typically buffered.} - \item{\code{spinner}}{Whether to show a reassuring spinner while the process -is running.} - \item{\code{timeout}}{Timeout for the process, in seconds, or as a \code{difftime} -object. If it is not finished before this, it will be killed.} - \item{\code{stdout_line_callback}}{\code{NULL}, or a function to call for every -line of the standard output. See \code{stdout_callback} and also more -below.} - \item{\code{stdout_callback}}{\code{NULL}, or a function to call for every chunk -of the standard output. A chunk can be as small as a single character. -At most one of \code{stdout_line_callback} and \code{stdout_callback} can be -non-\code{NULL}.} - \item{\code{stderr_line_callback}}{\code{NULL}, or a function to call for every -line of the standard error. See \code{stderr_callback} and also more -below.} - \item{\code{stderr_callback}}{\code{NULL}, or a function to call for every chunk -of the standard error. A chunk can be as small as a single character. -At most one of \code{stderr_line_callback} and \code{stderr_callback} can be -non-\code{NULL}.} - \item{\code{stderr_to_stdout}}{Whether to redirect the standard error to the -standard output. Specifying \code{TRUE} here will keep both in the -standard output, correctly interleaved. However, it is not possible -to deduce where pieces of the output were coming from. If this is -\code{TRUE}, the standard error callbacks (if any) are never called.} - \item{\code{env}}{Environment of the child process, a named character vector. -IF \code{NULL}, the environment of the parent is inherited.} - \item{\code{windows_verbatim_args}}{Whether to omit the escaping of the -command and the arguments on windows. Ignored on other platforms.} - \item{\code{windows_hide_window}}{Whether to hide the window of the -application on windows. Ignored on other platforms.} - \item{\code{encoding}}{The encoding to assume for \code{stdout} and -\code{stderr}. By default the encoding of the current locale is -used. Note that \code{processx} always reencodes the output of -both streams in UTF-8 currently.} - \item{\code{cleanup_tree}}{Whether to clean up the child process tree after -the process has finished.} - }} + +\item{...}{Extra arguments are passed to \code{process$new()}, see +\link[processx]{process}. Note that you cannot pass \code{stout} or \code{stderr} here, +because they are used internally by \code{run()}. You can use the +\code{stdout_callback}, \code{stderr_callback}, etc. arguments to manage +the standard output and error, or the \link[processx]{process} class directly +if you need more flexibility.} +} +\value{ +(invisible) \code{stdout} parsed through \code{\link[jsonlite:fromJSON]{jsonlite::fromJSON()}} } \description{ Run Azure CLI +Wraps the \href{https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest}{Azure Command-Line Interface (CLI)}. +} +\seealso{ +Other azure functions: +\code{\link{az_account}()}, +\code{\link{az_configure}()}, +\code{\link{az_login}()}, +\code{\link{az_webapp}()}, +\code{\link{include_app2_az}()}, +\code{\link{shiny_deploy_az}()}, +\code{\link{shiny_opts_az}()} } -\keyword{internal} +\concept{azure functions} diff --git a/man/az_configure.Rd b/man/az_configure.Rd new file mode 100644 index 0000000..53810a3 --- /dev/null +++ b/man/az_configure.Rd @@ -0,0 +1,60 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/azure.R +\name{az_configure} +\alias{az_configure} +\alias{az_configure_list} +\title{Manage Azure CLI configuration} +\usage{ +az_configure(name = NULL, resource_group = NULL, ...) + +az_configure_list(...) +} +\arguments{ +\item{name}{Name of the web app. +(In the Azure CLI, this argument is sometimes known as \code{name}, and sometimes as \code{web}).} + +\item{resource_group}{The \href{https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal}{Azure resource group} to which the shiny app should belong.} + +\item{...}{ + Arguments passed on to \code{\link[=az_cli_run]{az_cli_run}}, \code{\link[=az_cli_run]{az_cli_run}} + \describe{ + \item{\code{cmd}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{req}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{opt}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{add}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{echo_cmd}}{Whether to print the command to run to the screen.} + \item{\code{echo}}{Whether to print the standard output and error +to the screen. Note that the order of the standard output and error +lines are not necessarily correct, as standard output is typically +buffered.} + }} +} +\description{ +Overwrites defaults for the Azure CLI to \verb{.azure/} directory (\strong{side effect}), if arguments are provided. +Errors out if a default is missing. +} +\details{ +Because of an apparent \href{https://github.com/Azure/azure-cli/issues/15014}{bug}, the Azure CLI will always include defaults at \verb{~/.azure/}. +These hidden defaults can interfere with these functions. +Make sure that you have no default \code{name} and \code{resource_group} in the global azure default config in your \code{HOME} directory. +} +\section{Functions}{ +\itemize{ +\item \code{az_configure_list}: List defaults +}} + +\seealso{ +Other azure functions: +\code{\link{az_account}()}, +\code{\link{az_cli_run}()}, +\code{\link{az_login}()}, +\code{\link{az_webapp}()}, +\code{\link{include_app2_az}()}, +\code{\link{shiny_deploy_az}()}, +\code{\link{shiny_opts_az}()} +} +\concept{azure functions} diff --git a/man/az_login.Rd b/man/az_login.Rd new file mode 100644 index 0000000..88d114d --- /dev/null +++ b/man/az_login.Rd @@ -0,0 +1,42 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/azure.R +\name{az_login} +\alias{az_login} +\title{Log in to Azure} +\usage{ +az_login(...) +} +\arguments{ +\item{...}{ + Arguments passed on to \code{\link[=az_cli_run]{az_cli_run}} + \describe{ + \item{\code{cmd}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{req}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{opt}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{add}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{echo_cmd}}{Whether to print the command to run to the screen.} + \item{\code{echo}}{Whether to print the standard output and error +to the screen. Note that the order of the standard output and error +lines are not necessarily correct, as standard output is typically +buffered.} + }} +} +\description{ +Helper for interactive login. +Do not script this function unless you know what you are doing. +} +\seealso{ +Other azure functions: +\code{\link{az_account}()}, +\code{\link{az_cli_run}()}, +\code{\link{az_configure}()}, +\code{\link{az_webapp}()}, +\code{\link{include_app2_az}()}, +\code{\link{shiny_deploy_az}()}, +\code{\link{shiny_opts_az}()} +} +\concept{azure functions} diff --git a/man/az_webapp.Rd b/man/az_webapp.Rd index 145d728..47786ba 100644 --- a/man/az_webapp.Rd +++ b/man/az_webapp.Rd @@ -2,32 +2,38 @@ % Please edit documentation in R/azure.R \name{az_webapp} \alias{az_webapp} -\alias{az_account_set} -\alias{az_configure} \alias{az_webapp_create} +\alias{az_webapp_delete} +\alias{az_webapp_list} +\alias{az_webapp_show} \alias{az_webapp_deployment_slot_create} \alias{az_webapp_config_container_set} \alias{az_webapp_update} \alias{az_webapp_config_set} +\alias{az_webapp_config_container_show} \alias{az_webapp_config_appsettings_set} \alias{az_webapp_restart} -\title{Deploy a shiny app to Azure} +\title{Manage web apps} \usage{ -az_webapp(restart = FALSE, ...) - -az_account_set(subscription, ...) - -az_configure(name, resource_group, ...) +az_webapp(slot = NULL, restart = TRUE, ...) az_webapp_create( + name = NULL, plan, - name, - startup_file = NULL, + resource_group = NULL, deployment_container_image_name, + startup_file = NULL, + subscription = NULL, ... ) -az_webapp_deployment_slot_create(name, slot = NULL, ...) +az_webapp_delete(name = NULL, slot = NULL, ...) + +az_webapp_list(...) + +az_webapp_show(slot = NULL, ...) + +az_webapp_deployment_slot_create(name = NULL, resource_group = NULL, slot, ...) az_webapp_config_container_set( deployment_container_image_name, @@ -42,24 +48,51 @@ az_webapp_update(slot = NULL, ...) az_webapp_config_set(slot = NULL, ...) +az_webapp_config_container_show(slot = NULL, ...) + az_webapp_config_appsettings_set(slot = NULL, ...) az_webapp_restart(slot = NULL, ...) } \arguments{ -\item{restart}{whether to restart the web app.} +\item{slot}{The name of the \href{https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots}{deployment slot}. +Defaults to the production slot if not specified. +Only available for higher app service plan tiers.} -\item{\dots}{passed on to other functions or placeholder.} +\item{restart}{whether to restart the web app.} -\item{subscription}{Name or ID of the Azure subscription to which costs are billed. -According to an upvoted answer on Stack Overflow, \href{https://stackoverflow.com/questions/45661109/are-azure-subscription-id-aad-tenant-id-and-aad-app-client-id-considered-secre}{Azure subscription IDs need not be considered a secret or personal identifiable information (PII)}. -However, depending your applicable context and policies, you may want to provide this argument as a secret.} +\item{...}{ + Arguments passed on to \code{\link[=az_cli_run]{az_cli_run}}, \code{\link[=az_cli_run]{az_cli_run}} + \describe{ + \item{\code{cmd}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{req}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{opt}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{add}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{echo_cmd}}{Whether to print the command to run to the screen.} + \item{\code{echo}}{Whether to print the standard output and error +to the screen. Note that the order of the standard output and error +lines are not necessarily correct, as standard output is typically +buffered.} + }} + +\item{name}{Name of the web app. +(In the Azure CLI, this argument is sometimes known as \code{name}, and sometimes as \code{web}).} -\item{name}{Name of the web app.} +\item{plan}{Name or resource id of the app service plan.} \item{resource_group}{The \href{https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal}{Azure resource group} to which the shiny app should belong.} -\item{plan}{Name or resource id of the app service plan.} +\item{deployment_container_image_name}{The custom image name and optionally the tag name. +Image must +\itemize{ +\item include everything needed to run the shiny app, including shiny itself, +but \emph{does not} need to include shiny server or other software to route, load balance and serve shiny, +\item include an \code{ENTRYPOINT} and/or \href{https://docs.docker.com/engine/reference/builder/#cmd}{\code{CMD}} instruction to start shiny automatically (recommended), \emph{or} shiny must be started via the \code{startup_file} argument. +}} \item{startup_file}{\verb{docker run} \href{https://docs.docker.com/engine/reference/run/}{\verb{[COMMAND]}} to use inside of your custom image \code{deployment_container_image_name}. Defaults to \code{NULL}, in which case the container is expected to start up shiny automatically (recommended). @@ -78,17 +111,11 @@ The following \code{startup-file}s are \emph{invalid}: \item \code{"Rscript -e '1+1'"} (quoting of \verb{[EXPR]} would be treated as \verb{"Rscript -e '\\"1+1\\"'"}). }} -\item{deployment_container_image_name}{The custom image name and optionally the tag name. -Image must -\itemize{ -\item include everything needed to run the shiny app, including shiny itself, -but \emph{does not} need to include shiny server or other software to route, load balance and serve shiny, -\item include an \code{ENTRYPOINT} and/or \href{https://docs.docker.com/engine/reference/builder/#cmd}{\code{CMD}} instruction to start shiny automatically (recommended), \emph{or} shiny must be started via the \code{startup_file} argument. -}} +\item{subscription}{Name or ID of the Azure subscription to which costs are billed. +According to an upvoted answer on Stack Overflow, \href{https://stackoverflow.com/questions/45661109/are-azure-subscription-id-aad-tenant-id-and-aad-app-client-id-considered-secre}{Azure subscription IDs need not be considered a secret or personal identifiable information (PII)}. +However, depending your applicable context and policies, you may want to provide this argument as a secret. -\item{slot}{The name of the \href{https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots}{deployment slot}. -Defaults to the production slot if not specified. -Only available for higher app service plan tiers.} +To find out which subscriptions you are currently authorised to use, run \code{print(az_account_list())}.} \item{docker_registry_server_url}{The container registry server url. Defaults to \code{NULL}, in which case the azure default, \href{http://hub.docker.com}{docker hub} is used.} @@ -98,108 +125,41 @@ Defaults to \code{NULL} for public registries. Do not expose your credentials in public code; it's best to use secret environment variables.} } \description{ -Calls the \href{https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest}{Azure Command-Line Interface (CLI)} with defaults suitable for deploying a shiny app. -Wraps several steps. +Manage web apps } \section{Functions}{ \itemize{ -\item \code{az_account_set}: Setting subscription +\item \code{az_webapp_create}: Create a web app -\item \code{az_configure}: Set defaults for az CLI. -Does not leave side effects (\verb{.azure/}), but cleans up after itself. -Must be called repeatedly. -If a folder \code{.azure} is already in the working directory, those defaults are used and a warning is issued. +\item \code{az_webapp_delete}: Delete a web app -\item \code{az_webapp_create}: Creates a web app +\item \code{az_webapp_list}: List web apps -\item \code{az_webapp_deployment_slot_create}: Creates a deployment slot +\item \code{az_webapp_show}: Gets the details of a web app -\item \code{az_webapp_config_container_set}: Sets web app container settings +\item \code{az_webapp_deployment_slot_create}: Create a deployment slot -\item \code{az_webapp_update}: Sets web app tags +\item \code{az_webapp_config_container_set}: Set a web app container's settings -\item \code{az_webapp_config_set}: Sets web app configuration +\item \code{az_webapp_update}: Update a web app -\item \code{az_webapp_config_appsettings_set}: Sets web app appsetings +\item \code{az_webapp_config_set}: Set a web app's configuration -\item \code{az_webapp_restart}: Restarts the web app -}} +\item \code{az_webapp_config_container_show}: Get details of a web app container's settings -\examples{ -# hoad credentials used for testing -# authentication happens outside of r, see github actions main.yaml -plan <- "hoad" -resource_group <- "hoad" -subscription <- "f0dd3a37-0a4e-4e7f-9c9b-cb9f60146edc" - -# deploy shiny app using rocker image -az_webapp( - name = "hello-shiny", - # this image actually includes *more* than necessary - # for example, it includes shinyserver, but just shiny would suffice - deployment_container_image_name = "rocker/shiny:4.0.2", - # above image has no `ENTRYPOINT` and/or `CMD` to start shiny by default. - # so this `[COMMAND]` must be appended to `docker run` - startup_file = paste( - "Rscript", - # setting shiny options for azure manually - # equivalent to running shinycaas::az_webapp_shiny_opts() - "-e options(shiny.host='0.0.0.0',shiny.port=as.integer(Sys.getenv('PORT')))", - # remove getOption call https://github.com/subugoe/shinycaas/issues/37 - "-e shiny::runExample('01_hello',port=getOption('shiny.port'))" - ), - # replace below with your own credentials - plan = plan, - resource_group = resource_group, - subscription = subscription -) +\item \code{az_webapp_config_appsettings_set}: Set a web app's settings -# deploy shiny app to slot -az_webapp( - name = "hello-shiny", - # this image actually includes *more* than necessary - # for example, it includes shinyserver, but just shiny would suffice - deployment_container_image_name = "rocker/shiny:4.0.2", - # above image has no `ENTRYPOINT` and/or `CMD` to start shiny by default. - # so this `[COMMAND]` must be appended to `docker run` - startup_file = paste( - "Rscript", - # setting shiny options for azure manually - # equivalent to running shinycaas::az_webapp_shiny_opts() - "-e options(shiny.host='0.0.0.0',shiny.port=as.integer(Sys.getenv('PORT')))", - # remove getOption call https://github.com/subugoe/shinycaas/issues/37 - "-e shiny::runExample('04_mpg',port=getOption('shiny.port'))" - ), - # replace below with your own credentials - plan = plan, - resource_group = resource_group, - subscription = subscription, - slot = "mpg" -) +\item \code{az_webapp_restart}: Restarts the web app +}} -# below env vars and secrets are only available on github actions -if (is_github_actions()) { - # deploy shiny app using muggle image - # for an easier way to set these arguments, see the {muggle} package - az_webapp( - name = "old-faithful", - deployment_container_image_name = paste0( - "docker.pkg.github.com/subugoe/shinycaas/oldfaithful", ":", - ifelse(is_github_actions(), Sys.getenv("GITHUB_SHA"), "latest") - ), - plan = plan, - resource_group = resource_group, - subscription = subscription, - docker_registry_server_url = "https://docker.pkg.github.com", - docker_registry_server_user = Sys.getenv("GITHUB_ACTOR"), - docker_registry_server_password = Sys.getenv("GITHUB_TOKEN") - ) -} -} \seealso{ Other azure functions: -\code{\link{az_webapp_shiny_opts}()}, -\code{\link{az_webapp_show}()}, -\code{\link{include_app2_azure}()} +\code{\link{az_account}()}, +\code{\link{az_cli_run}()}, +\code{\link{az_configure}()}, +\code{\link{az_login}()}, +\code{\link{include_app2_az}()}, +\code{\link{shiny_deploy_az}()}, +\code{\link{shiny_opts_az}()} } \concept{azure functions} diff --git a/man/az_webapp_show.Rd b/man/az_webapp_show.Rd deleted file mode 100644 index 564d557..0000000 --- a/man/az_webapp_show.Rd +++ /dev/null @@ -1,25 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/azure.R -\name{az_webapp_show} -\alias{az_webapp_show} -\title{Get details of a web app} -\usage{ -az_webapp_show(...) -} -\arguments{ -\item{...}{ - Arguments passed on to \code{\link[=az_webapp]{az_webapp}} - \describe{ - \item{\code{restart}}{whether to restart the web app.} - }} -} -\description{ -Get details of a web app -} -\seealso{ -Other azure functions: -\code{\link{az_webapp_shiny_opts}()}, -\code{\link{az_webapp}()}, -\code{\link{include_app2_azure}()} -} -\concept{azure functions} diff --git a/man/include_app2_azure.Rd b/man/include_app2_az.Rd similarity index 74% rename from man/include_app2_azure.Rd rename to man/include_app2_az.Rd index 66fc160..e5cf52a 100644 --- a/man/include_app2_azure.Rd +++ b/man/include_app2_az.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/azure.R -\name{include_app2_azure} -\alias{include_app2_azure} +\name{include_app2_az} +\alias{include_app2_az} \title{Embed a shiny app hosted on Azure} \usage{ -include_app2_azure(slot = NULL, ...) +include_app2_az(slot = NULL, ...) } \arguments{ \item{slot}{The name of the \href{https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots}{deployment slot}. @@ -25,8 +25,12 @@ Embed a shiny app hosted on Azure } \seealso{ Other azure functions: -\code{\link{az_webapp_shiny_opts}()}, -\code{\link{az_webapp_show}()}, -\code{\link{az_webapp}()} +\code{\link{az_account}()}, +\code{\link{az_cli_run}()}, +\code{\link{az_configure}()}, +\code{\link{az_login}()}, +\code{\link{az_webapp}()}, +\code{\link{shiny_deploy_az}()}, +\code{\link{shiny_opts_az}()} } \concept{azure functions} diff --git a/man/shiny_deploy_az.Rd b/man/shiny_deploy_az.Rd new file mode 100644 index 0000000..bc7a5da --- /dev/null +++ b/man/shiny_deploy_az.Rd @@ -0,0 +1,143 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/azure.R +\name{shiny_deploy_az} +\alias{shiny_deploy_az} +\title{Deploy a shiny app to Azure} +\usage{ +shiny_deploy_az(...) +} +\arguments{ +\item{...}{ + Arguments passed on to \code{\link[=az_account]{az_account}}, \code{\link[=az_configure]{az_configure}}, \code{\link[=az_webapp]{az_webapp}}, \code{\link[=az_webapp_create]{az_webapp_create}}, \code{\link[=az_webapp_config_container_set]{az_webapp_config_container_set}}, \code{\link[=az_cli_run]{az_cli_run}} + \describe{ + \item{\code{subscription}}{Name or ID of the Azure subscription to which costs are billed. +According to an upvoted answer on Stack Overflow, \href{https://stackoverflow.com/questions/45661109/are-azure-subscription-id-aad-tenant-id-and-aad-app-client-id-considered-secre}{Azure subscription IDs need not be considered a secret or personal identifiable information (PII)}. +However, depending your applicable context and policies, you may want to provide this argument as a secret. + +To find out which subscriptions you are currently authorised to use, run \code{print(az_account_list())}.} + \item{\code{name}}{Name of the web app. +(In the Azure CLI, this argument is sometimes known as \code{name}, and sometimes as \code{web}).} + \item{\code{resource_group}}{The \href{https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal}{Azure resource group} to which the shiny app should belong.} + \item{\code{restart}}{whether to restart the web app.} + \item{\code{slot}}{The name of the \href{https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots}{deployment slot}. +Defaults to the production slot if not specified. +Only available for higher app service plan tiers.} + \item{\code{plan}}{Name or resource id of the app service plan.} + \item{\code{startup_file}}{\verb{docker run} \href{https://docs.docker.com/engine/reference/run/}{\verb{[COMMAND]}} to use inside of your custom image \code{deployment_container_image_name}. +Defaults to \code{NULL}, in which case the container is expected to start up shiny automatically (recommended). +For details on the shiny startup command, see the examples. + +\strong{The \verb{[EXPR]} (anything after \code{-e}) must not be quoted, and must not contain spaces (\href{https://github.com/subugoe/shinycaas/issues/27}{#27})}. +For example, the following \code{startup-file}s are valid (if nonsensical, because they don't start a shiny app) +\itemize{ +\item \code{"Rscript -e 1+1"} (no spaces) +\item \code{"Rscript -e print('foo')"} (no spaces, no quoting \emph{of} the \verb{[EXPR]}) +} + +The following \code{startup-file}s are \emph{invalid}: +\itemize{ +\item \code{"Rscript -e 1 + 1"} (spaces inside \verb{[EXPR]}) +\item \code{"Rscript -e '1+1'"} (quoting of \verb{[EXPR]} would be treated as \verb{"Rscript -e '\\"1+1\\"'"}). +}} + \item{\code{deployment_container_image_name}}{The custom image name and optionally the tag name. +Image must +\itemize{ +\item include everything needed to run the shiny app, including shiny itself, +but \emph{does not} need to include shiny server or other software to route, load balance and serve shiny, +\item include an \code{ENTRYPOINT} and/or \href{https://docs.docker.com/engine/reference/builder/#cmd}{\code{CMD}} instruction to start shiny automatically (recommended), \emph{or} shiny must be started via the \code{startup_file} argument. +}} + \item{\code{docker_registry_server_url}}{The container registry server url. +Defaults to \code{NULL}, in which case the azure default, \href{http://hub.docker.com}{docker hub} is used.} + \item{\code{docker_registry_server_user}}{Credentials for private container registries. +Defaults to \code{NULL} for public registries. +Do not expose your credentials in public code; it's best to use secret environment variables.} + \item{\code{docker_registry_server_password}}{Credentials for private container registries. +Defaults to \code{NULL} for public registries. +Do not expose your credentials in public code; it's best to use secret environment variables.} + \item{\code{cmd}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{req}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{opt}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{add}}{Command, required, optional, additional parameters, as for \code{\link[processx:run]{processx::run()}} +\code{add} parameters are reserved for the user to pass down additional arguments.} + \item{\code{echo_cmd}}{Whether to print the command to run to the screen.} + \item{\code{echo}}{Whether to print the standard output and error +to the screen. Note that the order of the standard output and error +lines are not necessarily correct, as standard output is typically +buffered.} + }} +} +\description{ +Calls the with defaults suitable for deploying a shiny app. +Wraps several steps. +} +\examples{ +# hoad credentials used for testing +# authentication happens outside of r, see github actions main.yaml +# during testing .azure/ is not available, so we have to set that here +plan <- "hoad" +resource_group <- "hoad" + +# deploy shiny app using rocker image +shiny_deploy_az( + name = "hello-shiny", + # this image actually includes *more* than necessary + # for example, it includes shinyserver, but just shiny would suffice + deployment_container_image_name = "rocker/shiny:4.0.2", + # above image has no `ENTRYPOINT` and/or `CMD` to start shiny by default. + # so this `[COMMAND]` must be appended to `docker run` + startup_file = paste( + "Rscript", + # setting shiny options for azure manually + # equivalent to running shinycaas::shiny_opts_az() + "-e options(shiny.host='0.0.0.0',shiny.port=as.integer(Sys.getenv('PORT')))", + # remove getOption call https://github.com/subugoe/shinycaas/issues/37 + "-e shiny::runExample('01_hello',port=getOption('shiny.port'))" + ), + plan = plan, + resource_group = resource_group +) + +# deploy shiny app to slot +shiny_deploy_az( + name = "hello-shiny", + deployment_container_image_name = "rocker/shiny:4.0.2", + startup_file = paste( + "Rscript", + "-e options(shiny.host='0.0.0.0',shiny.port=as.integer(Sys.getenv('PORT')))", + "-e shiny::runExample('04_mpg',port=getOption('shiny.port'))" + ), + plan = plan, + slot = "mpg" # a more suitable slot name might be "dev" or "staging" +) + +# below env vars and secrets are only available on github actions +if (is_github_actions()) { + # deploy shiny app using muggle image + # for an easier way to set these arguments, see the {muggle} package + shiny_deploy_az( + name = "old-faithful", + deployment_container_image_name = paste0( + "docker.pkg.github.com/subugoe/shinycaas/oldfaithful", ":", + ifelse(is_github_actions(), Sys.getenv("GITHUB_SHA"), "latest") + ), + plan = plan, + docker_registry_server_url = "https://docker.pkg.github.com", + docker_registry_server_user = Sys.getenv("GITHUB_ACTOR"), + docker_registry_server_password = Sys.getenv("GITHUB_TOKEN") + ) +} +} +\seealso{ +Other azure functions: +\code{\link{az_account}()}, +\code{\link{az_cli_run}()}, +\code{\link{az_configure}()}, +\code{\link{az_login}()}, +\code{\link{az_webapp}()}, +\code{\link{include_app2_az}()}, +\code{\link{shiny_opts_az}()} +} +\concept{azure functions} diff --git a/man/az_webapp_shiny_opts.Rd b/man/shiny_opts_az.Rd similarity index 71% rename from man/az_webapp_shiny_opts.Rd rename to man/shiny_opts_az.Rd index 00930de..00fc4ca 100644 --- a/man/az_webapp_shiny_opts.Rd +++ b/man/shiny_opts_az.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/azure.R -\name{az_webapp_shiny_opts} -\alias{az_webapp_shiny_opts} +\name{shiny_opts_az} +\alias{shiny_opts_az} \title{Shiny options for Azure} \usage{ -az_webapp_shiny_opts() +shiny_opts_az() } \description{ Set shiny options as \href{https://docs.microsoft.com/en-us/azure/app-service/containers/configure-custom-container}{required for an Azure Webapp}: @@ -16,12 +16,16 @@ If your image suggests \code{EXPOSE}d ports, that may be respected by Azure (und } } \details{ -You can also set these options manually as in the below example. +You can also set these options manually, see \code{\link[=az_webapp]{az_webapp()}}. } \seealso{ Other azure functions: -\code{\link{az_webapp_show}()}, +\code{\link{az_account}()}, +\code{\link{az_cli_run}()}, +\code{\link{az_configure}()}, +\code{\link{az_login}()}, \code{\link{az_webapp}()}, -\code{\link{include_app2_azure}()} +\code{\link{include_app2_az}()}, +\code{\link{shiny_deploy_az}()} } \concept{azure functions} diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index 94b63e6..d8f4505 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -18,6 +18,7 @@ reference: desc: > Deploy a Shiny App to [Microsoft Azure Web Apps for Containers](https://azure.microsoft.com/en-us/services/app-service/containers/) - contents: + - ends_with("az") - has_concept("azure functions") articles: - title: Example Apps diff --git a/shinycaas.Rproj b/shinycaas.Rproj index aaa62a5..766b3b2 100644 --- a/shinycaas.Rproj +++ b/shinycaas.Rproj @@ -5,8 +5,13 @@ SaveWorkspace: No AlwaysSaveHistory: Default EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 Encoding: UTF-8 +RnwWeave: knitr +LaTeX: pdfLaTeX + AutoAppendNewline: Yes StripTrailingWhitespace: Yes LineEndingConversion: Posix diff --git a/tests/testthat/setup-azure.R b/tests/testthat/setup-azure.R index 361c31f..1545381 100644 --- a/tests/testthat/setup-azure.R +++ b/tests/testthat/setup-azure.R @@ -1,11 +1,11 @@ # hoad credentials used for testing # authentication happens outside of r, see github actions main.yaml +# during testing .azure/ is not available, so we have to set that here plan <- "hoad" resource_group <- "hoad" -subscription <- "f0dd3a37-0a4e-4e7f-9c9b-cb9f60146edc" # deploy shiny app using rocker image -az_webapp( +shiny_deploy_az( name = "hello-shiny", # this image actually includes *more* than necessary # for example, it includes shinyserver, but just shiny would suffice @@ -15,53 +15,39 @@ az_webapp( startup_file = paste( "Rscript", # setting shiny options for azure manually - # equivalent to running shinycaas::az_webapp_shiny_opts() + # equivalent to running shinycaas::shiny_opts_az() "-e options(shiny.host='0.0.0.0',shiny.port=as.integer(Sys.getenv('PORT')))", # remove getOption call https://github.com/subugoe/shinycaas/issues/37 "-e shiny::runExample('01_hello',port=getOption('shiny.port'))" ), - # replace below with your own credentials plan = plan, - resource_group = resource_group, - subscription = subscription + resource_group = resource_group ) # deploy shiny app to slot -az_webapp( +shiny_deploy_az( name = "hello-shiny", - # this image actually includes *more* than necessary - # for example, it includes shinyserver, but just shiny would suffice deployment_container_image_name = "rocker/shiny:4.0.2", - # above image has no `ENTRYPOINT` and/or `CMD` to start shiny by default. - # so this `[COMMAND]` must be appended to `docker run` startup_file = paste( "Rscript", - # setting shiny options for azure manually - # equivalent to running shinycaas::az_webapp_shiny_opts() "-e options(shiny.host='0.0.0.0',shiny.port=as.integer(Sys.getenv('PORT')))", - # remove getOption call https://github.com/subugoe/shinycaas/issues/37 "-e shiny::runExample('04_mpg',port=getOption('shiny.port'))" ), - # replace below with your own credentials plan = plan, - resource_group = resource_group, - subscription = subscription, - slot = "mpg" + slot = "mpg" # a more suitable slot name might be "dev" or "staging" ) # below env vars and secrets are only available on github actions if (is_github_actions()) { # deploy shiny app using muggle image # for an easier way to set these arguments, see the {muggle} package - az_webapp( + shiny_deploy_az( name = "old-faithful", deployment_container_image_name = paste0( "docker.pkg.github.com/subugoe/shinycaas/oldfaithful", ":", ifelse(is_github_actions(), Sys.getenv("GITHUB_SHA"), "latest") ), plan = plan, - resource_group = resource_group, - subscription = subscription, docker_registry_server_url = "https://docker.pkg.github.com", docker_registry_server_user = Sys.getenv("GITHUB_ACTOR"), docker_registry_server_password = Sys.getenv("GITHUB_TOKEN") diff --git a/tests/testthat/test-azure.R b/tests/testthat/test-azure.R index 798645d..a249b17 100644 --- a/tests/testthat/test-azure.R +++ b/tests/testthat/test-azure.R @@ -1,9 +1,42 @@ -test_that("multiplication works", { - skip_if_not( - condition = is_github_actions(), - message = "Only running deployment test on GitHub Actions." +context("azure") + +# wrap all +test_that("deployment works", { + expect_equal(1, 1) +}) + +# az account +test_that("az account works", { + checkmate::expect_subset( + x = "subugoe", + choices = az_account()$name, + empty.ok = FALSE + ) +}) + +# az configure +test_that("az configure works", { + old_defaults <- az_configure_list() + expect_identical( + az_configure(name = "foo", resource_group = "bar"), + list(resource_group = "bar", name = "foo") ) - # TODO test here whether new app actually runs #22 - # must wait for successful redeploy from helper #29 - expect_equal(2 * 2, 4) + expect_identical( + az_configure_list(), + list(resource_group = "bar", name = "foo") + ) + fs::file_delete(".azure/config") + # this does not appear to work + # expect_error(az_configure()) + withr::defer(do.call(az_configure, old_defaults)) +}) + +# az webapp +test_that("az webapp create works", { + expect_identical(1, 1) +}) + +# helpers +test_that("cli commands work", { + checkmate::expect_list(az_cli_run(cmd = "version", opt = "--verbose")) })