diff --git a/.Rbuildignore b/.Rbuildignore index e48db62..71125cb 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -1,14 +1,11 @@ -^shinycaas\.Rproj$ -^\.Rproj\.user$ -^LICENSE\.md$ -[.]code-workspace$ -^codecov\.yml$ +^.*\.Rproj$ ^\.github$ -^CODE_OF_CONDUCT\.md$ -^_pkgdown\.yml$ +^LICENSE\.md$ +^builder_img$ +Dockerfile +.dockerignore ^docs$ ^pkgdown$ -Dockerfile +^codecov\.yml$ ^vignettes/ ^\.azure$ -man-roxygen diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index f5a8d1a..0000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,47 +0,0 @@ -# Contributing to shinycaas - -This outlines how to propose a change to shinycaas. -For more detailed info about contributing to this, and other tidyverse packages, please see the -[**development contributing guide**](https://rstd.io/tidy-contrib). - -## Fixing typos - -You can fix typos, spelling mistakes, or grammatical errors in the documentation directly using the GitHub web interface, as long as the changes are made in the _source_ file. -This generally means you'll need to edit [roxygen2 comments](https://roxygen2.r-lib.org/articles/roxygen2.html) in an `.R`, not a `.Rd` file. -You can find the `.R` file that generates the `.Rd` by reading the comment in the first line. - -## Bigger changes - -If you want to make a bigger change, it's a good idea to first file an issue and make sure someone from the team agrees that it’s needed. -If you’ve found a bug, please file an issue that illustrates the bug with a minimal -[reprex](https://www.tidyverse.org/help/#reprex) (this will also help you write a unit test, if needed). - -### Pull request process - -* Fork the package and clone onto your computer. If you haven't done this before, we recommend using `usethis::create_from_github("subugoe/shinycaas", fork = TRUE)`. - -* Install all development dependences with `devtools::install_dev_deps()`, and then make sure the package passes R CMD check by running `devtools::check()`. - If R CMD check doesn't pass cleanly, it's a good idea to ask for help before continuing. -* Create a Git branch for your pull request (PR). We recommend using `usethis::pr_init("brief-description-of-change")`. - -* Make your changes, commit to git, and then create a PR by running `usethis::pr_push()`, and following the prompts in your browser. - The title of your PR should briefly describe the change. - The body of your PR should contain `Fixes #issue-number`. - -* For user-facing changes, add a bullet to the top of `NEWS.md` (i.e. just below the first header). Follow the style described in . - -### Code style - -* New code should follow the tidyverse [style guide](https://style.tidyverse.org). - You can use the [styler](https://CRAN.R-project.org/package=styler) package to apply these styles, but please don't restyle code that has nothing to do with your PR. - -* We use [roxygen2](https://cran.r-project.org/package=roxygen2), with [Markdown syntax](https://cran.r-project.org/web/packages/roxygen2/vignettes/rd-formatting.html), for documentation. - -* We use [testthat](https://cran.r-project.org/package=testthat) for unit tests. - Contributions with test cases included are easier to accept. - -## Code of Conduct - -Please note that the shinycaas project is released with a -[Contributor Code of Conduct](CODE_OF_CONDUCT.md). By contributing to this -project you agree to abide by its terms. diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 0f86df9..306bdeb 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -8,7 +8,7 @@ jobs: shell: Rscript {0} container: # this needs to be the same as in Dockerfile - image: subugoe/muggle-buildtime-onbuild:a94fcb785886af96d440b4bcd7f47c01162d7f5e + image: subugoe/muggle-buildtime-onbuild:f7fb6146d8712c4bffb024f4d4f40c40ffab5598 env: MUGGLE_PKG_NAME: ${{ github.event.repository.name }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -53,7 +53,7 @@ jobs: - name: Test Coverage run: covr::codecov() - name: Build Package Website - run: pkgdown::build_site(override = list(new_process = FALSE)) + run: muggle::build_site2(new_process = FALSE) - uses: docker/build-push-action@v1 name: Build Buildtime Image with: diff --git a/.gitignore b/.gitignore index 099661c..96523a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,37 @@ -.Rproj.user +# History files .Rhistory +.Rapp.history + +# Session Data files .RData + +# User-specific files +.Ruserdata + +# Example code in package build process +*-Ex.R + +# Output files from R CMD build +/*.tar.gz + +# Output files from R CMD check +/*.Rcheck/ + +# RStudio files +.Rproj.user/ + +# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 +.httr-oauth + +# knitr and R markdown default cache directories +*_cache/ +/cache/ + +# Temporary files created by R markdown +*.utf8.md +*.knit.md + +# R Environment Variables +.Renviron docs -.azure/config +.Rproj.user diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index b36903f..0000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,128 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity and -orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, -and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall -community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or -advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email -address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a -professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards -of acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies -when an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at [INSERT CONTACT -METHOD]. All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, -available at https://www.contributor-covenant.org/version/2/0/ -code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at https:// -www.contributor-covenant.org/translations. diff --git a/DESCRIPTION b/DESCRIPTION index 2ebb2ec..fe11313 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -2,13 +2,18 @@ Package: shinycaas Title: Deploy shiny applications to container as a service (CaaS) Version: 0.0.0.9000 Authors@R: - c(person(given = "Maximilian", - family = "Held", - role = c("aut", "cre"), - email = "info@maxheld.de", - comment = c(ORCID = "0000-0002-4703-5388")), - person(given = c(subugoe = "Göttingen State and University Library"), - role = c("cph", "fnd")) + c( + person( + given = "Maximilian", + family = "Held", + role = c("aut", "cre"), + email = "info@maxheld.de", + comment = c(ORCID = "0000-0002-4703-5388") + ), + person( + given = "Göttingen State and University Library", + role = c("cph", "fnd") + ) ) Description: Deploying shiny apps to container as a service (CaaS) products on public cloud vendors. License: MIT + file LICENSE @@ -19,18 +24,14 @@ RoxygenNote: 7.1.1 URL: https://subugoe.github.io/shinycaas, https://github.com/subugoe/shinycaas BugReports: https://github.com/subugoe/shinycaas/issues Suggests: - testthat, - withr, + testthat (>= 3.0.0), shiny (>= 1.5.0), - subugoetheme, - fs + subugoetheme Imports: - processx (>= 3.4.3), - cli (>= 2.0.2), + AzureAppService, checkmate (>= 2.0.0), htmltools (>= 0.5.0), - jsonlite (>= 1.7.0) + cli (>= 2.2.0) Remotes: - subugoe/subugoetheme@1.0.0 -SystemRequirements: - Azure CLI (>= 2.9) (https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) + subugoe/subugoetheme@1.0.0, + subugoe/AzureAppService@fd3d0795ece511c4ffe054e774bf3784f226dbc4 diff --git a/Dockerfile b/Dockerfile index a2eddad..39b3cda 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG MUGGLE_TAG=a94fcb785886af96d440b4bcd7f47c01162d7f5e +ARG MUGGLE_TAG=f7fb6146d8712c4bffb024f4d4f40c40ffab5598 FROM subugoe/muggle-buildtime-onbuild:${MUGGLE_TAG} as buildtime FROM subugoe/muggle-runtime-onbuild:${MUGGLE_TAG} as runtime CMD shinycaas::az_webapp_shiny_opts(); shinycaas::runOldFaithful() diff --git a/NAMESPACE b/NAMESPACE index fa3bed1..35b6dfa 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,13 +1,11 @@ # 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(getenv2) export(include_app2) -export(include_app2_az) export(is_github_actions) export(runOldFaithful) -export(shiny_deploy_az) export(shiny_opts_az) +importFrom(AzureAppService,az_configure) +importFrom(AzureAppService,az_webapp) diff --git a/R/azure.R b/R/azure.R index 2226d9e..44b04e4 100644 --- a/R/azure.R +++ b/R/azure.R @@ -1,459 +1,17 @@ -# wrap all ==== #' Deploy a shiny app to Azure -#' -#' Calls the with defaults suitable for deploying a shiny app. -#' Wraps several steps. -#' -#' @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 -#' -#' @template azure -#' #' @export -shiny_deploy_az <- function(...) { - az_account(...) - az_configure(...) - az_webapp(...) -} +#' @name az_webapp +AzureAppService::az_webapp -# az login ==== -#' Log in to Azure -#' -#' Helper for interactive login. -#' Do not script this function unless you know what you are doing. -#' -#' @template azure -#' +#' Set Azure defaults #' @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_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 ...") - az_cli_run( - cmd = c("account", "set"), - req = c("--subscription", subscription), - ... - ) -} - -# 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. -#' -#' @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) - ), - ... - ) - 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 - } - 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 Create a web app -#' -#' @inheritParams az_configure -#' -#' @param plan -#' Name or resource id of the app service plan. -#' -#' @param startup_file -#' `docker run` [`[COMMAND]`](https://docs.docker.com/engine/reference/run/) to use inside of your custom image `deployment_container_image_name`. -#' Defaults to `NULL`, in which case the container is expected to start up shiny automatically (recommended). -#' For details on the shiny startup command, see the examples. -#' -#' **The `[EXPR]` (anything after `-e`) must not be quoted, and must not contain spaces ([#27](https://github.com/subugoe/shinycaas/issues/27))**. -#' For example, the following `startup-file`s are valid (if nonsensical, because they don't start a shiny app) -#' - `"Rscript -e 1+1"` (no spaces) -#' - `"Rscript -e print('foo')"` (no spaces, no quoting *of* the `[EXPR]`) -#' -#' 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(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( - 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 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 = 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 Set a web app container's settings -#' -#' @param deployment_container_image_name -#' The custom image name and optionally the tag name. -#' Image must -#' - include everything needed to run the shiny app, including shiny itself, -#' but *does not* need to include shiny server or other software to route, load balance and serve shiny, -#' - include an `ENTRYPOINT` and/or [`CMD`](https://docs.docker.com/engine/reference/builder/#cmd) instruction to start shiny automatically (recommended), *or* shiny must be started via the `startup_file` argument. -#' -#' @param docker_registry_server_url -#' The container registry server url. -#' Defaults to `NULL`, in which case the azure default, [docker hub](http://hub.docker.com) is used. -#' -#' @param docker_registry_server_user,docker_registry_server_password -#' Credentials for private container registries. -#' Defaults to `NULL` for public registries. -#' Do not expose your credentials in public code; it's best to use secret environment variables. -az_webapp_config_container_set <- function(deployment_container_image_name, - docker_registry_server_url = NULL, - docker_registry_server_user = NULL, - docker_registry_server_password = NULL, - slot = NULL, - ...) { - checkmate::assert_string(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( - 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 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( - 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 Set a web app's configuration -az_webapp_config_set <- function(slot = NULL, ...) { - cli::cli_alert_info("Setting web app configuration ...") - 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 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( - 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( - cmd = c("webapp", "restart"), - opt = c(if (!is.null(slot)) c("--slot", slot)), - ... - ) -} +#' @name az_configure +AzureAppService::az_configure # helpers ==== -#' Run Azure CLI -#' 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", - # 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 = 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 #' #' Set shiny options as [required for an Azure Webapp](https://docs.microsoft.com/en-us/azure/app-service/containers/configure-custom-container): @@ -481,21 +39,8 @@ shiny_opts_az <- function() { } old_opts <- options( shiny.port = port, # defined on azure - shiny.host = "0.0.0.0" # to five azure access to inner container + shiny.host = "0.0.0.0" # to give azure access to inner container ) # TODO might revert back to old_opts with # withr::defer(options(old_opts), envir = parent.frame(2)) } - -#' Embed a shiny app hosted on Azure -#' -#' @inheritDotParams include_app2 -#' -#' @inheritParams az_webapp -#' -#' @family azure functions -#' -#' @export -include_app2_az <- function(slot = NULL, ...) { - NULL -} diff --git a/R/helpers.R b/R/helpers.R index 14af57d..edee394 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -9,3 +9,36 @@ is_github_actions <- function() { Sys.getenv("GITHUB_ACTIONS") == "true" } + + +# TODO migrate to ghactions repo https://github.com/subugoe/shinycaas/issues/52 +#' Get GitHub Actions Environment Variables or Local Equivalent +#' +#' Using [environment variables set on GitHub Actions](https://docs.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables) *or* their local equivalents can sometimes be useful, for example in testing. +#' When running inside GitHub Actions ([is_github_actions()]), returns the respective environment variable. +#' Otherwise, returns a `local` equivalent or errors out. +#' +#' @param ghactions +#' The [GitHub Actions environment variable](https://docs.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables) to get when `is_github_actions() == TRUE`, as a character string. +#' +#' @param local +#' The value to be used when `is_github_actions() == FALSE`. +#' +#' @keywords internal +#' +#' @export +getenv2 <- function(ghactions, local) { + checkmate::assert_string(ghactions) + if (is_github_actions()) { + res <- Sys.getenv(ghactions) + if (res == "") { + stop("Environment variable", ghactions, "is empty on GitHub Actions.") + } + } else { + res <- local + if (res == "") { + stop("The local equivalent to the", ghactions, "environment variable is empty.") + } + } + res +} diff --git a/R/shinycaas-package.R b/R/shinycaas-package.R index b30bbda..8880a87 100644 --- a/R/shinycaas-package.R +++ b/R/shinycaas-package.R @@ -1,4 +1,5 @@ #' @keywords internal +#' @importFrom AzureAppService az_webapp az_configure "_PACKAGE" # The following block is used by usethis to automatically manage diff --git a/README.md b/README.md index 1190b27..46a6262 100644 --- a/README.md +++ b/README.md @@ -4,36 +4,11 @@ [![Main](https://github.com/subugoe/shinycaas/workflows/.github/workflows/main.yaml/badge.svg)](https://github.com/subugoe/shinycaas/actions) [![Codecov test coverage](https://codecov.io/gh/subugoe/shinycaas/branch/master/graph/badge.svg)](https://codecov.io/gh/subugoe/shinycaas?branch=master) -[![R build status](https://github.com/subugoe/shinycaas/workflows/R-CMD-check/badge.svg)](https://github.com/subugoe/shinycaas/actions) +[![CRAN status](https://www.r-pkg.org/badges/version/shinycaas)](https://CRAN.R-project.org/package=shinycaas) [![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://www.tidyverse.org/lifecycle/#experimental) -The goal of shinycaas is to ... - -## Installation - -You can install the released version of shinycaas from [CRAN](https://CRAN.R-project.org) with: - -``` r -install.packages("shinycaas") -``` - -## Example - -This is a basic example which shows you how to solve a common problem: - -``` r -library(shinycaas) -## basic example code -``` - -## System Requirements - -This package calls the [Microsoft Azure](https://azure.microsoft.com/) Command-Line Interface (CLI). -To deploy to Azure, you need to [install the Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) any machine from which you want to deploy your shiny app. -There's no need to install the Azure CLI into your *production* image; you only need it at deploy time. -If you only deploy from GitHub Actions (recommended) you do not need to install anything; the Azure CLI is [included](https://docs.github.com/en/actions/reference/software-installed-on-github-hosted-runners) in all GitHub-hosted runners. - +The goal of shinycaas is to make it easy to deploy shiny apps to Container-as-a-Service (CaaS) products in the public cloud. ### Limitations of [shinyapps.io](https://www.shinyapps.io) diff --git a/man-roxygen/azure.R b/man-roxygen/azure.R deleted file mode 100644 index 3415347..0000000 --- a/man-roxygen/azure.R +++ /dev/null @@ -1,2 +0,0 @@ -#' @family azure functions -#' @inheritDotParams az_cli_run diff --git a/man/az_account.Rd b/man/az_account.Rd deleted file mode 100644 index 6da9d49..0000000 --- a/man/az_account.Rd +++ /dev/null @@ -1,66 +0,0 @@ -% 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 deleted file mode 100644 index f35a1a7..0000000 --- a/man/az_cli_run.Rd +++ /dev/null @@ -1,53 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/azure.R -\name{az_cli_run} -\alias{az_cli_run} -\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( - cmd, - req = NULL, - opt = NULL, - add = NULL, - echo_cmd = FALSE, - echo = FALSE, - ... -) -} -\arguments{ -\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{...}{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}()} -} -\concept{azure functions} diff --git a/man/az_configure.Rd b/man/az_configure.Rd index 53810a3..6ae7245 100644 --- a/man/az_configure.Rd +++ b/man/az_configure.Rd @@ -1,60 +1,15 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/azure.R +\docType{import} \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.} - }} -} +\title{Set Azure defaults} +\keyword{internal} \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 +These objects are imported from other packages. Follow the links +below to see their documentation. + +\describe{ + \item{AzureAppService}{\code{\link[AzureAppService]{az_configure}}} }} -\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 deleted file mode 100644 index 88d114d..0000000 --- a/man/az_login.Rd +++ /dev/null @@ -1,42 +0,0 @@ -% 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 47786ba..4ba1af1 100644 --- a/man/az_webapp.Rd +++ b/man/az_webapp.Rd @@ -1,165 +1,84 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/azure.R +\docType{import} \name{az_webapp} \alias{az_webapp} -\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{Manage web apps} -\usage{ -az_webapp(slot = NULL, restart = TRUE, ...) - -az_webapp_create( - name = NULL, - plan, - resource_group = NULL, - deployment_container_image_name, - startup_file = NULL, - subscription = NULL, - ... +\title{Deploy a shiny app to Azure} +\examples{ +# login +# the equivalent of (interactive) az_login is run outside of R + +# subscription +# the azure login action automatically retrieves the enabled subscriptions GitHub Actions + +# resource group +az_configure(resource_group = "hoad") +# you can also pass this argument to shiny_deploy_az for the one-call deployment +# you can also set this once and commit the resulting .azure/config +# but this is not easily available during testing, so it's set from R here +# because several apps are deployed below, `name` is set for each of them + +# plan +plan <- "hoad" +# there's no way to set a plan default + +# deploy shiny app using rocker image +az_configure(name = "hello-shiny") +az_webapp( + # 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 ) -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, - docker_registry_server_url = NULL, - docker_registry_server_user = NULL, - docker_registry_server_password = NULL, - slot = NULL, - ... +# deploy shiny app to slot +az_webapp( + 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('05_sliders',port=getOption('shiny.port'))" + ), + plan = plan, + slot = "sliders" # a more suitable slot name might be "dev" or "staging" ) -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, ...) +# deploy shiny app using private image, here from muggle package +# below env vars and secrets are only available on github actions +if (is_github_actions()) { + # for an easier way to set these arguments, see the {muggle} package + az_configure(name = "old-faithful") + az_webapp( + 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") + ) } -\arguments{ -\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{restart}{whether to restart the web app.} - -\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{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{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). -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{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{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{docker_registry_server_user, 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.} +# cleanup (necessary for testing) +unlink(".azure", recursive = TRUE) } +\keyword{internal} \description{ -Manage web apps -} -\section{Functions}{ -\itemize{ -\item \code{az_webapp_create}: Create a web app - -\item \code{az_webapp_delete}: Delete a web app - -\item \code{az_webapp_list}: List web apps +These objects are imported from other packages. Follow the links +below to see their documentation. -\item \code{az_webapp_show}: Gets the details of a web app - -\item \code{az_webapp_deployment_slot_create}: Create a deployment slot - -\item \code{az_webapp_config_container_set}: Set a web app container's settings - -\item \code{az_webapp_update}: Update a web app - -\item \code{az_webapp_config_set}: Set a web app's configuration - -\item \code{az_webapp_config_container_show}: Get details of a web app container's settings - -\item \code{az_webapp_config_appsettings_set}: Set a web app's settings - -\item \code{az_webapp_restart}: Restarts the web app +\describe{ + \item{AzureAppService}{\code{\link[AzureAppService]{az_webapp}}} }} -\seealso{ -Other azure functions: -\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/getenv2.Rd b/man/getenv2.Rd new file mode 100644 index 0000000..b774e5a --- /dev/null +++ b/man/getenv2.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/helpers.R +\name{getenv2} +\alias{getenv2} +\title{Get GitHub Actions Environment Variables or Local Equivalent} +\usage{ +getenv2(ghactions, local) +} +\arguments{ +\item{ghactions}{The \href{https://docs.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables}{GitHub Actions environment variable} to get when \code{is_github_actions() == TRUE}, as a character string.} + +\item{local}{The value to be used when \code{is_github_actions() == FALSE}.} +} +\description{ +Using \href{https://docs.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables}{environment variables set on GitHub Actions} \emph{or} their local equivalents can sometimes be useful, for example in testing. +When running inside GitHub Actions (\code{\link[=is_github_actions]{is_github_actions()}}), returns the respective environment variable. +Otherwise, returns a \code{local} equivalent or errors out. +} +\keyword{internal} diff --git a/man/include_app2_az.Rd b/man/include_app2_az.Rd deleted file mode 100644 index e5cf52a..0000000 --- a/man/include_app2_az.Rd +++ /dev/null @@ -1,36 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/azure.R -\name{include_app2_az} -\alias{include_app2_az} -\title{Embed a shiny app hosted on Azure} -\usage{ -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}. -Defaults to the production slot if not specified. -Only available for higher app service plan tiers.} - -\item{...}{ - Arguments passed on to \code{\link[=include_app2]{include_app2}} - \describe{ - \item{\code{src}}{The URL of the shiny app to embed.} - \item{\code{name}}{Name of the iframe.} - \item{\code{height}}{The dimensions of the iframe as CSS units.} - \item{\code{width}}{The dimensions of the iframe as CSS units.} - }} -} -\description{ -Embed a shiny app hosted on Azure -} -\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{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 deleted file mode 100644 index bc7a5da..0000000 --- a/man/shiny_deploy_az.Rd +++ /dev/null @@ -1,143 +0,0 @@ -% 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/shiny_opts_az.Rd b/man/shiny_opts_az.Rd index 00fc4ca..1acc516 100644 --- a/man/shiny_opts_az.Rd +++ b/man/shiny_opts_az.Rd @@ -18,14 +18,4 @@ If your image suggests \code{EXPOSE}d ports, that may be respected by Azure (und \details{ You can also set these options manually, see \code{\link[=az_webapp]{az_webapp()}}. } -\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_deploy_az}()} -} \concept{azure functions} diff --git a/pkgdown/_pkgdown.yml b/pkgdown/_pkgdown.yml index d8f4505..ed8d028 100644 --- a/pkgdown/_pkgdown.yml +++ b/pkgdown/_pkgdown.yml @@ -1,4 +1,3 @@ -url: https://subugoe.github.io/shinycaas/ authors: Maximilian Held: href: https://www.maxheld.de diff --git a/shinycaas.code-workspace b/shinycaas.code-workspace deleted file mode 100644 index 5709732..0000000 --- a/shinycaas.code-workspace +++ /dev/null @@ -1,8 +0,0 @@ -{ - "folders": [ - { - "path": "." - } - ], - "settings": {} -} diff --git a/tests/testthat/setup-azure.R b/tests/testthat/setup-azure.R index 1545381..32ade70 100644 --- a/tests/testthat/setup-azure.R +++ b/tests/testthat/setup-azure.R @@ -1,12 +1,23 @@ -# 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 +# login +# the equivalent of (interactive) az_login is run outside of R + +# subscription +# the azure login action automatically retrieves the enabled subscriptions GitHub Actions + +# resource group +az_configure(resource_group = "hoad") +# you can also pass this argument to shiny_deploy_az for the one-call deployment +# you can also set this once and commit the resulting .azure/config +# but this is not easily available during testing, so it's set from R here +# because several apps are deployed below, `name` is set for each of them + +# plan plan <- "hoad" -resource_group <- "hoad" +# there's no way to set a plan default # deploy shiny app using rocker image -shiny_deploy_az( - name = "hello-shiny", +az_configure(name = "hello-shiny") +az_webapp( # 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", @@ -20,29 +31,27 @@ shiny_deploy_az( # 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 + plan = plan ) # deploy shiny app to slot -shiny_deploy_az( - name = "hello-shiny", +az_webapp( 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'))" + "-e shiny::runExample('05_sliders',port=getOption('shiny.port'))" ), plan = plan, - slot = "mpg" # a more suitable slot name might be "dev" or "staging" + slot = "sliders" # a more suitable slot name might be "dev" or "staging" ) +# deploy shiny app using private image, here from muggle package # 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", + az_configure(name = "old-faithful") + az_webapp( deployment_container_image_name = paste0( "docker.pkg.github.com/subugoe/shinycaas/oldfaithful", ":", ifelse(is_github_actions(), Sys.getenv("GITHUB_SHA"), "latest") @@ -53,3 +62,6 @@ if (is_github_actions()) { docker_registry_server_password = Sys.getenv("GITHUB_TOKEN") ) } + +# cleanup (necessary for testing) +unlink(".azure", recursive = TRUE) diff --git a/tests/testthat/test-azure.R b/tests/testthat/test-azure.R deleted file mode 100644 index a249b17..0000000 --- a/tests/testthat/test-azure.R +++ /dev/null @@ -1,42 +0,0 @@ -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") - ) - 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")) -}) diff --git a/tests/testthat/test-helpers.R b/tests/testthat/test-helpers.R new file mode 100644 index 0000000..869440a --- /dev/null +++ b/tests/testthat/test-helpers.R @@ -0,0 +1,26 @@ +empty_envvar <- "FOOBARZAP123" +test_that("getenv2 works locally", { + skip_on_ci() + expect_error(getenv2( + ghactions = empty_envvar, + local = Sys.getenv(empty_envvar) # likely empty + )) + expect_equal( + getenv2(ghactions = empty_envvar, local = "bingo"), + "bingo" + ) +}) +test_that("getenv2 works on github actions", { + skip_if_not( + is_github_actions(), + message = "Not running on GitHub Actions" + ) + expect_error(getenv2( + ghactions = empty_envvar, + local = Sys.getenv(empty_envvar) # likely empty + )) + expect_equal( + getenv2(ghactions = "GITHUB_SERVER_URL", local = "bingo"), + "https://github.com" + ) +})