From b6c683de6d4ff18360e6e813d83f0b1c00674b29 Mon Sep 17 00:00:00 2001 From: Tan Ho Date: Fri, 19 Sep 2025 15:25:35 -0400 Subject: [PATCH 1/5] convert vignette rmd code chunks to plain R chunks to prevent eval, closes #301 --- NEWS.md | 2 + README.Rmd | 6 +- README.md | 2 +- vignettes/articles/google-compute-engine.Rmd | 53 +++++++++-------- .../articles/managing-tokens-securely.Rmd | 18 +++--- vignettes/auth-from-web.Rmd | 6 +- vignettes/gargle-auth-in-client-package.Rmd | 34 +++++------ vignettes/get-api-credentials.Rmd | 6 +- vignettes/how-gargle-gets-tokens.Rmd | 28 ++++----- vignettes/non-interactive-auth.Rmd | 57 ++++++++----------- vignettes/oauth-client-not-app.Rmd | 22 +++---- vignettes/request-helper-functions.Rmd | 24 ++++---- vignettes/troubleshooting.Rmd | 10 ++-- 13 files changed, 130 insertions(+), 138 deletions(-) diff --git a/NEWS.md b/NEWS.md index 870b41c5..6dfab866 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # gargle (development version) +* In vignettes, convert all ````{r eval=FALSE}` chunks to plain ````r` chunks to prevent any chance of vignette code evaluation while maintaining R syntax highlighting (#301, @tanho63) + # gargle 1.6.0 * When retrying a request, the messaging reveals more detail about the failed diff --git a/README.Rmd b/README.Rmd index aea06984..824afbf3 100644 --- a/README.Rmd +++ b/README.Rmd @@ -46,13 +46,13 @@ See the [articles](https://gargle.r-lib.org/articles/) for holistic advice on ho You can install the released version of gargle from [CRAN](https://CRAN.R-project.org) with: -```{r eval = FALSE} +```r install.packages("gargle") ``` And the development version from [GitHub](https://github.com/) with: -```{r eval = FALSE} +```r # install.packages("pak") pak::pak("r-lib/gargle") ``` @@ -61,7 +61,7 @@ pak::pak("r-lib/gargle") gargle is a low-level package and does not do anything visibly exciting on its own. But here's a bit of usage in an interactive scenario where a user confirms they want to use a specific Google identity and loads an OAuth2 token. -```{r eval = FALSE} +```r library(gargle) token <- token_fetch() diff --git a/README.md b/README.md index c0a6b26c..549f222b 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ out <- response_process(resp) out <- out[["items"]][1:8] sort(vapply(out, function(x) x[["family"]], character(1))) -#> [1] "Inter" "Lato" "Material Icons" "Montserrat" +#> [1] "Inter" "Lato" "Material Icons" "Montserrat" #> [5] "Noto Sans JP" "Open Sans" "Poppins" "Roboto" ``` diff --git a/vignettes/articles/google-compute-engine.Rmd b/vignettes/articles/google-compute-engine.Rmd index 2a6fbeea..645538c1 100644 --- a/vignettes/articles/google-compute-engine.Rmd +++ b/vignettes/articles/google-compute-engine.Rmd @@ -5,8 +5,7 @@ title: "Google Compute Engine" ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, - comment = "#>", - eval = FALSE + comment = "#>" ) ``` @@ -36,7 +35,7 @@ I have done the required setup for this package: Having done this setup, this is how attaching the package looks: -```{r} +```r library(googleComputeEngineR) #> ✔ Setting scopes to https://www.googleapis.com/auth/cloud-platform #> ✔ Successfully auto-authenticated via /path/to/that/json/mentioned/above.json @@ -50,7 +49,7 @@ I don't think this has any direct connection to instance scopes for VMs created You can see your current instances with `gce_list_instances()`: -```{r} +```r gce_list_instances() #> ==Google Compute Engine Instance List== #> name machineType status zone externalIP creationTimestamp @@ -66,7 +65,7 @@ The above reflects how things look after I've been mucking around a bit and have Here's my basic way of creating a VM: -```{r} +```r vm <- gce_vm( template = "rstudio", name = "cerebral-lion", @@ -80,22 +79,22 @@ I can no longer remember why I settled on `predefined_type = "e2-standard-4"`. Here's what you'll see: -```{r} +```r #> ── ## VM Template: ' rstudio' running at http://{IP_ADDRESS} ───────────────────────────────────────────────────── #> ℹ 2023-04-13 12:03:05 > On first boot, wait a few minutes for docker container to install before logging in. #> ==Google Compute Engine Instance== -#> +#> #> Name: cerebral-lion #> Created: 2023-04-13 12:02:44 #> Machine Type: e2-standard-4 #> Status: RUNNING #> Zone: us-west1-a #> External IP: {IP_ADDRESS} -#> Disks: +#> Disks: #> deviceName type mode boot autoDelete #> 1 cerebral-lion-boot-disk PERSISTENT READ_WRITE TRUE TRUE -#> -#> Metadata: +#> +#> Metadata: #> key value #> 2 template rstudio #> 3 google-logging-enabled true @@ -107,7 +106,7 @@ Here's what you'll see: You can then log in to RStudio Server at the given `{IP_ADDRESS}`. Helpful snippets for getting that on the clipboard: -```{r} +```r # if you, e.g., just created `vm` paste0("http://", gce_get_external_ip(vm)) |> clipr::write_clip() @@ -120,7 +119,7 @@ paste0("http://", gce_get_external_ip("cerebral-lion")) |> Under the hood, googleComputeEngineR is inserting its own default choices for the associated service account and scopes. It's actually as if you had done: -```{r} +```r gce_vm( ..., serviceAccounts = list( @@ -139,21 +138,21 @@ To learn more: name email aliases #> 1 {EMAIL_OF_THE_DEFAULT_SERVICE_ACCOUNT} {EMAIL_OF_THE_DEFAULT_SERVICE_ACCOUNT} default @@ -169,7 +168,7 @@ So there will only ever be 1 actual service account identify, but you might see Let's get a token with `token_fetch()` and inspect it. -```{r} +```r t <- token_fetch() #> trying `token_fetch()` #> ... @@ -180,7 +179,7 @@ t <- token_fetch() #> GCE service account name: "default" #> GCE access token scopes: "...cloud-platform" t -#> +#> #> ── ────────────────────────────────────────────────────────────────────────────────────────────────────── #> scopes: ...cloud-platform #> credentials: access_token, expires_in, token_type @@ -190,7 +189,7 @@ By default, `credentials_gce()` uses the `default` service account and the `"clo What if we want to do something with the Google Drive API and we request that scope? -```{r} +```r t <- token_fetch(c( "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/drive" @@ -212,7 +211,7 @@ t <- token_fetch(c( #> GCE service account name: "default" #> GCE access token scopes: "...cloud-platform" t -#> +#> #> ── ────────────────────────────────────────────────────────────────────────────────────────────────────── #> scopes: ...cloud-platform #> credentials: access_token, expires_in, token_type @@ -224,7 +223,7 @@ We get a token, but still with only the `"cloud-platform"` scope, because the Dr And, indeed, this lack of an explicit Drive scope means that, e.g., the googledrive package can't do operations that require auth: -```{r} +```r library(googledrive) drive_find() #> attempt to access internal gargle data from: googledrive @@ -252,7 +251,7 @@ If you're not actively working on the VM, you should at least suspend it. Then you could resume it to pick up where you left off. To ensure that you aren't incurring any charges, you should stop the machine, but then you'll have to start over if you've, e.g., installed dev packages or downloaded/created any files. -```{r} +```r gce_vm_suspend("cerebral-lion") gce_vm_resume("cerebral-lion") gce_vm_stop("cerebral-lion") @@ -261,7 +260,7 @@ gce_vm_stop("cerebral-lion") It's a good idea to check that you've done whatever you intended with the instance. Check its status here: -```{r} +```r gce_list_instances() #> ==Google Compute Engine Instance List== #> name machineType status zone externalIP creationTimestamp @@ -279,7 +278,7 @@ AFAICT googleComputeEngineR only helps you set scopes at the time of VM creation It seems possible to change scopes for pre-existing instance as long as it is stopped, so maybe that could be a feature request for googleComputeEngineR (or maybe I'm overlooking that there's already a way to do this). Further reading: . -```{r} +```r vm <- gce_vm( template = "rstudio", name = "trustful-bull", @@ -301,7 +300,7 @@ vm <- gce_vm( I get the IP address, log in to RStudio Server, and install the desired version of gargle (not shown). `gce_instance_service_accounts()` shows that we have, in fact, managed to change the `scopes` available to the default service account: -```{r} +```r gce_instance_service_accounts() #> name email aliases #> 1 {EMAIL_OF_THE_DEFAULT_SERVICE_ACCOUNT} {EMAIL_OF_THE_DEFAULT_SERVICE_ACCOUNT} default @@ -314,7 +313,7 @@ gce_instance_service_accounts() We see this in actual tokens as well. Note that we get `"drive"` scope *even if we don't ask for it*. -```{r} +```r t <- token_fetch() #> trying `token_fetch()` #> ... @@ -328,7 +327,7 @@ t <- token_fetch() #> GCE service account name: "default" #> GCE access token scopes: "...cloud-platform, ...drive" t -#> +#> #> ── ────────────────────────────────────────────────────────────────────────────────────────────────────── #> scopes: ...cloud-platform, ...drive #> credentials: access_token, expires_in, token_type @@ -336,7 +335,7 @@ t And, as one would expect, it's now possible to work with the googledrive package. -```{r} +```r library(googledrive) drive_find() #> # A dribble: 0 × 3 diff --git a/vignettes/articles/managing-tokens-securely.Rmd b/vignettes/articles/managing-tokens-securely.Rmd index 3fb47406..188fc4ba 100644 --- a/vignettes/articles/managing-tokens-securely.Rmd +++ b/vignettes/articles/managing-tokens-securely.Rmd @@ -122,7 +122,7 @@ You will be interested in `secret_encrypt_json()` if you want to encrypt a servi `secret_encrypt_json()` takes 3 arguments: * `json`: probably the path to a JSON file, but a JSON string is also - acceptable. + acceptable. * `path`: The path to write the encrypted JSON to. Technically this is optional, but this function mostly exists to write to file. * `key`: The name of the environment variable that holds the encryption key. @@ -130,7 +130,7 @@ You will be interested in `secret_encrypt_json()` if you want to encrypt a servi This example shows how googledrive's testing credentials are placed inside the package source. `googledrive-testing.json` is a JSON file downloaded for a service account managed via the [Google API / Cloud Platform console](https://console.cloud.google.com/project): -```{r eval = FALSE} +```r secret_encrypt_json( json = "~/some/place/where/I/keep/secret/stuff/googledrive-testing.json", path = "inst/secret/googledrive-testing.json", @@ -163,7 +163,7 @@ If at all possible, use a service account instead. This example shows how an encrypted googlesheets4 user token could be placed inside the `.secrets/` directory of a project, e.g. a Shiny app intended for deployment. -```{r eval = FALSE} +```r library(googlesheets4) dir.create(".secrets") @@ -227,7 +227,7 @@ It should come as no surprise that `secret_encrypt_json()` and `secret_write_rds Recall that in the example above we encrypted the JSON specifying a service account token, for use in CI by googledrive. Here's how you would use `secret_decrypt_json()` to decrypt that token and direct googledrive to use it: -```{r eval = FALSE} +```r library(googledrive) drive_auth( @@ -243,7 +243,7 @@ drive_auth( Recall that in the example above we encrypted a googlesheets4 user token, for use inside something like a deployed Shiny app. Here's how you would use `secret_read_rds()` to decrypt that token and direct googlesheets4 to use it: -```{r eval = FALSE} +```r library(googlesheets4) gs4_auth(token = gargle::secret_read_rds( @@ -268,22 +268,22 @@ You do want to rig things for graceful, informative failure in this case. likely to benefit from this is you, i.e. when you're trying to figure out why your app isn't working. It's nice to have a clear signal that the encryption key is unavailable instead of some mysterious deployment failure. - + #### Condition on key availability `secret_has_key("SOMETHING_KEY")` reports whether the `"SOMETHING_KEY"` environment variable is defined. In a deployed data product, you might want to call `secret_has_key()` before any attempt to decrypt a secret. If the encryption key is not available, report that finding and arrange to do something graceful instead of erroring, especially in some cryptic, difficult-to-debug way. - + #### Automatic skips The `secret_*` functions have a built-in feature such that, if they are called during testing, when the encryption key is unavailable, that test is skipped. That behaviour is implemented in the internal helper `secret_get_key()`, which looks something like this: -```{r eval = FALSE} +```r secret_get_key <- function(envvar) { key <- Sys.getenv(envvar) - + if (identical(key, "")) { if (is_testing()) { msg <- glue("Env var {envvar} not defined.") diff --git a/vignettes/auth-from-web.Rmd b/vignettes/auth-from-web.Rmd index 5d51caa2..24ba94f4 100644 --- a/vignettes/auth-from-web.Rmd +++ b/vignettes/auth-from-web.Rmd @@ -98,7 +98,7 @@ Packages like googledrive and bigrquery aim to make auth "just work" for most us However, it is always possible to initiate auth yourself, which gives you the opportunity to specify non-default values of certain parameters. Here's how you could request OOB auth, using googledrive as an example: -```{r eval = FALSE} +```r library(googledrive) drive_auth(use_oob = TRUE) @@ -111,7 +111,7 @@ drive_find(n_max = 5) If you know that you *always* want to use OOB, as a user or within a project, the best way to express this is to set the `"gargle_oob_default"` option. -```{r eval = FALSE} +```r options(gargle_oob_default = TRUE) ``` @@ -187,7 +187,7 @@ If we are using OOB auth, the decision between conventional or pseudo-OOB is mad Packages that use a built-in tidyverse OAuth client (googledrive, googlesheets4, and bigrquery) should automatically select a "web" client on RStudio Server, Posit Cloud, Posit Workbench, and Google Colaboratory and an "installed" client otherwise. If you need to explicitly request a "web" client in some other setting, you can use the global option `"gargle_oauth_client_type"`: -```{r eval = FALSE} +```r options(gargle_oauth_client_type = "web") ``` diff --git a/vignettes/gargle-auth-in-client-package.Rmd b/vignettes/gargle-auth-in-client-package.Rmd index 125e552a..77160d42 100644 --- a/vignettes/gargle-auth-in-client-package.Rmd +++ b/vignettes/gargle-auth-in-client-package.Rmd @@ -26,13 +26,13 @@ Getting a token requires several pieces of information and there are stark diffe * Overall config: OAuth client and API key. Who provides? * Token-level properties: Google identity (email) and scopes. * Request-level: Who manages tokens and injects them into requests? - + ### User-facing auth In googledrive, the main user-facing auth function is `googledrive::drive_auth()`. Here is its definition (at least approximately, remember this is static code): -```{r, eval = FALSE} +```r # googledrive:: drive_auth <- function(email = gargle::gargle_oauth_email(), path = NULL, @@ -74,19 +74,19 @@ The internal `.auth` object maintains googledrive's auth state and is explained A client package can use an internal object of class `gargle::AuthState` to hold the auth state. In googledrive, the main auth file defines a placeholder `.auth` object: -```{r eval = FALSE} +```r .auth <- NULL ``` The actual initialization happens in `.onLoad()`: -```{r} +```r .onLoad <- function(libname, pkgname) { utils::assignInMyNamespace( ".auth", gargle::init_AuthState(package = "googledrive", auth_active = TRUE) ) - + # other stuff } ``` @@ -108,7 +108,7 @@ The client is a component that most users do not even know about and they are co There is a field in the `.auth` auth state to hold the OAuth `client`. Exported auth helpers, `drive_oauth_client()` and `drive_auth_configure()`, retrieve and modify the current client to support users who want to (or must) take that level of control. -```{r, eval = FALSE} +```r library(googledrive) # first: download the OAuth client as a JSON file @@ -140,7 +140,7 @@ For example, this is a great way to read a Google Sheet that is world-readable o The user can provide their own API key via `drive_auth_configure(api_key =)` and retrieve that value with `drive_api_key()`, just as with the OAuth client. The API key is stored in the `api_key` field of the `.auth` auth state. -```{r, eval = FALSE} +```r library(googledrive) drive_auth_configure(api_key = "123456789") @@ -164,7 +164,7 @@ Since users may have more than one Google account, it's quite likely that they w That explains why `drive_auth()` has the optional `email` argument that lets users proactively specify their identity. `drive_auth()` is usually called indirectly upon first need, but a user can also call it proactively in order to specify their target `email`: -```{r eval = FALSE} +```r # googledrive:: drive_auth(email = "janedoe_work@gmail.com") ``` @@ -181,7 +181,7 @@ A client package can usually pick sensible default scopes, that will support wha Here's a reminder of the signature of `googledrive::drive_auth()`: -```{r, eval = FALSE} +```r # googledrive:: drive_auth <- function(email = gargle::gargle_oauth_email(), path = NULL, @@ -194,7 +194,7 @@ drive_auth <- function(email = gargle::gargle_oauth_email(), googledrive ships with a default scope, but a motivated user could call `drive_auth()` preemptively at the start of the session and request different scopes. For example, if they intend to only read data and want to guard against inadvertent file modification, they might opt for the `drive.readonly` scope. -```{r, eval = FALSE} +```r # googledrive:: drive_auth(scopes = "https://www.googleapis.com/auth/drive.readonly") ``` @@ -230,7 +230,7 @@ I focus on early use, by the naive user, with the OAuth flow. When the user first calls a high-level googledrive function such as `drive_find()`, a Drive request is ultimately generated with a call to `googledrive::request_generate()`. Here is its definition, at least approximately: -```{r eval = FALSE} +```r # googledrive:: request_generate <- function(endpoint = character(), params = list(), @@ -260,11 +260,11 @@ request_generate <- function(endpoint = character(), ``` `googledrive::request_generate()` is a thin wrapper around `gargle::request_develop()` and `gargle::request_build()` that only implements details specific to googledrive, before delegating to more general functions in gargle. -The `vignette("request-helper-functions")` documents these gargle functions. +The `vignette("request-helper-functions")` documents these gargle functions. `googledrive::request_generate()` gets a token with `drive_token()`, which is defined like so: -```{r eval = FALSE} +```r # googledrive:: drive_token <- function() { if (isFALSE(.auth$auth_active)) { @@ -279,7 +279,7 @@ drive_token <- function() { where `drive_has_token()` in a helper defined as: -```{r eval = FALSE} +```r # googledrive:: drive_has_token <- function() { inherits(.auth$cred, "Token2.0") @@ -297,7 +297,7 @@ Multiple gargle-using packages can use a shared token by obtaining a suitably sc For example, the default scope requested by googledrive is also sufficient for operations available in googlesheets4. You could use a shared token like so: -```{r eval = FALSE} +```r library(googledrive) library(googlesheets4) @@ -310,7 +310,7 @@ gs4_auth(token = drive_token()) # registers token with googlesheets4 ``` It is important to make sure that the token-requesting package (googledrive, above) is using an OAuth client for which all the necessary APIs and scopes are enabled. - + ## Auth interface The exported functions like `drive_auth()`, `drive_token()`, etc. constitute the auth interface between googledrive and gargle and are centralized in [`tidyverse/googledrive/R/drive_auth.R`](https://github.com/tidyverse/googledrive/blob/main/R/drive_auth.R). @@ -369,7 +369,7 @@ One reason for a user to call `drive_auth()` directly and proactively is to swit `drive_auth()` accepts an `email` argument, which is honored when gargle determines if there is already a suitable token on hand. Here is a sketch of how a user could switch identities during a session, possibly non-interactive: -```{r eval = FALSE} +```r library(googledrive) drive_auth(email = "janedoe_work@gmail.com") diff --git a/vignettes/get-api-credentials.Rmd b/vignettes/get-api-credentials.Rmd index 43b41031..b62ac777 100644 --- a/vignettes/get-api-credentials.Rmd +++ b/vignettes/get-api-credentials.Rmd @@ -78,7 +78,7 @@ Package maintainers might want to build an API key in as a fallback, possibly ta Package users could register an API key for use with a wrapper package. For example, in googlesheets4, one would use `googlesheets4::gs4_auth_configure()` to store a key for use in downstream requests, i.e. after a call to `googlesheets4::gs4_deauth()`: -```{r eval = FALSE} +```r library(googlesheets4) gs4_auth_configure(api_key = "YOUR_API_KEY_GOES_HERE") @@ -126,7 +126,7 @@ Package maintainers might want to build this client in as a fallback, possibly t Package users could register their own client for use with a wrapper package. For example, in googledrive, one would use `googledrive::drive_auth_configure()` to do this: -```{r, eval = FALSE} +```r library(googledrive) google_client <- gargle::gargle_oauth_client_from_json( @@ -188,7 +188,7 @@ Authors of wrapper packages can use the symmetric encryption strategy described You could provide the token's filepath to a wrapper package's main auth function, e.g.: -```{r eval = FALSE} +```r # googledrive drive_auth(path = "/path/to/your/service-account-token.json") ``` diff --git a/vignettes/how-gargle-gets-tokens.Rmd b/vignettes/how-gargle-gets-tokens.Rmd index 704e1c15..9c2bb339 100644 --- a/vignettes/how-gargle-gets-tokens.Rmd +++ b/vignettes/how-gargle-gets-tokens.Rmd @@ -45,7 +45,7 @@ The objective of `token_fetch()` is to allow package developers to take responsi The signature of `token_fetch()` is very simple and, therefore, not very informative: -```{r, eval = FALSE} +```r token_fetch(scopes, ...) ``` @@ -75,7 +75,7 @@ Read more in the docs for `gargle_verbosity()`. The first function tried is `credentials_byo_oauth2()`. Here's how a call to `token_fetch()` might work: -```{r, eval = FALSE} +```r token_fetch(token = ) credentials_byo_oauth2( @@ -97,7 +97,7 @@ If `token` is not provided or if it doesn't satisfy these requirements, we fail The next function tried is `credentials_service_account()`. Here's how a call to `token_fetch()` with service account inputs plays out: -```{r, eval = FALSE} +```r token_fetch(scopes = , path = "/path/to/your/service-account.json") # credentials_byo_oauth2() fails because no `token`, @@ -135,7 +135,7 @@ If your service account token requests fail with "Bad Request" inside a containe The next function tried is `credentials_external_account()`. Here's how a call to `token_fetch()` with an external account inputs plays out: -```{r, eval = FALSE} +```r token_fetch(scopes = , path = "/path/to/your/external-account.json") # credentials_byo_oauth2() fails because no `token`, @@ -168,7 +168,7 @@ Here is some Google documentation about workload identity federation and the spe The next function tried is `credentials_app_default()`. Here's how a call to `token_fetch()` might work: -```{r, eval = FALSE} +```r token_fetch(scopes = ) # credentials_byo_oauth2() fails because no `token`, @@ -186,7 +186,7 @@ The credentials themselves are conventional service account, external account, o The hope is to make auth "just work" for someone working on Google-provided infrastructure or who has used Google tooling to get started, such as the [`gcloud` command line tool](https://cloud.google.com/sdk/gcloud). A sequence of paths is consulted, which we describe here, with some abuse of notation. ALL_CAPS represents the value of an environment variable. -```{r, eval = FALSE} +```r ${GOOGLE_APPLICATION_CREDENTIALS} ${CLOUDSDK_CONFIG}/application_default_credentials.json @@ -220,7 +220,7 @@ The JSON configuration for an external account is not actually sensitive and thi The next function tried is `credentials_gce()`. Here's how a call to `token_fetch()` might work: -```{r, eval = FALSE} +```r token_fetch(scopes = ) # or perhaps token_fetch(scopes = , service_account = ) @@ -251,7 +251,7 @@ If this seems to happening to you and it's not what you want, see the last secti The next and final function tried is `credentials_user_oauth2()`. Here's how a call to `token_fetch()` might work: -```{r, eval = FALSE} +```r token_fetch(scopes = ) # credentials_byo_oauth2() fails because no `token`, @@ -279,7 +279,7 @@ Per the Google User Data Policy 14 tokens found in this gargle OAuth cache: #> ~/Library/Caches/gargle @@ -388,7 +388,7 @@ Let's say you want to prevent `token_fetch()` from even trying one specific auth You can remove a specific credential function from the registry. Here's how to do this for the scenario described above, where you want to skip GCE-specific auth: -```{r eval = FALSE} +```r gargle::cred_funs_add(credentials_gce = NULL) ``` diff --git a/vignettes/non-interactive-auth.Rmd b/vignettes/non-interactive-auth.Rmd index 51fefdc2..d9b094d6 100644 --- a/vignettes/non-interactive-auth.Rmd +++ b/vignettes/non-interactive-auth.Rmd @@ -7,15 +7,6 @@ vignette: > %\VignetteEncoding{UTF-8} --- -```{r, include = FALSE} -knitr::opts_chunk$set( - collapse = TRUE, - comment = "#>", - eval = FALSE, - purl = FALSE -) -``` - Here we describe how to do auth with a package that uses gargle, without requiring any user interaction. This comes up in a wide array of contexts, ranging from simple rendering of a local R Markdown document to deploying a data product on a remote server. @@ -56,7 +47,7 @@ When you embed tokens in the project and deploy, remember, that, by default, the TL;DR is that you need to successfully authenticate *once* in an interactive session and then, in your code, give gargle permission to use a token it finds in the cache. These sorts of commands achieve that: -```{r} +```r # Approach #1: use an option. # Either specify the user: options(gargle_oauth_email = "jenny@example.com") @@ -102,7 +93,7 @@ GCE allows applications to get an OAuth access token from its metadata server an This token request can be made for specific scopes and, in general, most wrapper packages will indeed be asking for specific scopes relevant to the API they access. Consider the signature of `googledrive::drive_auth()`: -```{r} +```r drive_auth <- function(email = gargle::gargle_oauth_email(), path = NULL, scopes = "https://www.googleapis.com/auth/drive", @@ -124,7 +115,7 @@ Be aware that you might also need to explicitly grant the service account an app Finally, if you want to opt-out of using the default service account and, instead, auth as a normal user, even though you are on GCE, that is also possible. One way to achieve that is to remove `credentials_gce()` from the set of auth functions tried by `gargle::token_fetch()` by executing this command before any explicit or implicit auth happens: -```{r} +```r # removes `credentials_gce()` from gargle's registry gargle::cred_funs_add(credentials_gce = NULL) ``` @@ -178,7 +169,7 @@ Documentation around `GKEStartPodOperator()` within Cloud Composer can be found Here is example code that you might execute in your Docker container: -```{r} +```r options(gargle.gce.use_ip = TRUE) t <- gargle::credentials_gce("my-service-key@my-project.iam.gserviceaccount.com") # ... do authenticated stuff with the token t ... @@ -189,7 +180,7 @@ At the time of writing the `service_account` argument is not exposed in the usua So if you need to use a non-`default` service account, you need to call `credentials_gce()` directly and pass that token to `PKG_auth()`: Here's an example of how that might look: -```{r} +```r library(PKG) options(gargle.gce.use_ip = TRUE) @@ -220,7 +211,7 @@ If you're not working in cloud context with automatic access to a service accoun Example using googledrive: -```{r} +```r library(googledrive) drive_auth(path = "/path/to/your/service-account-token.json") @@ -253,7 +244,7 @@ Some details: It is also possible to get a token with an explicit call to, e.g., `credentials_service_account()` and then pass that token to the auth function: -```{r} +```r t <- gargle::credentials_service_account( path = "/path/to/your/service-account-token.json", scopes = ..., @@ -277,7 +268,7 @@ Your token should just get discovered upon first need. For troubleshooting purposes, you can set a gargle option to see verbose output about the execution of `gargle::token_fetch()`: -```{r} +```r options(gargle_verbosity = "debug") ``` @@ -287,7 +278,7 @@ withr-style convenience helpers also exist: `with_gargle_verbosity()` and `local If you somehow have the OAuth token you want to use as an R object, you can provide it directly to the `token` argument of the main auth function. Example using googledrive: -```{r} +```r library(googledrive) my_oauth_token <- # some process that results in the token you want to use @@ -298,7 +289,7 @@ gargle caches each OAuth user token it obtains to an `.rds` file, by default. If you know the filepath to the token you want to use, you could use `readRDS()` to read it and provide as the `token` argument to the wrapper's auth function. Example using googledrive: -```{r} +```r # googledrive drive_auth(token = readRDS("/path/to/your/oauth-token.rds")) ``` @@ -327,7 +318,7 @@ There are many ways to do this. We'll work several examples using that convey th **Step 1**: Get that first token. You must run your code at least once, interactively, do the auth dance, and allow gargle to store the token in its cache. -```{r} +```r library(googledrive) # do anything that triggers auth @@ -353,37 +344,37 @@ You have two choices to make: This sets an option that allows gargle to use cached tokens whenever there's a unique match: -```{r} +```r options(gargle_oauth_email = TRUE) ``` This sets an option to use tokens associated with a specific email address: -```{r} +```r options(gargle_oauth_email = "jenny@example.com") ``` This sets an option to use tokens associated with an email address with a specific domain: -```{r} +```r options(gargle_oauth_email = "*@example.com") ``` This gets a token *right now* and allows the use of a matching token, using googledrive as an example: -```{r} +```r drive_auth(email = TRUE) ``` This gets a token *right now*, for the user with a specific email address: -```{r} +```r drive_auth(email = "jenny@example.com") ``` This gets a token *right now*, first checking the cache for a token associated with a specific domain: -```{r} +```r drive_auth(email = "*@example.com") ``` @@ -394,7 +385,7 @@ This is like the previous example, but with an added twist: we use a project-lev **Step 1**: Obtain the token intended for non-interactive use and make sure it's cached in a (hidden) directory of the current project. Using googledrive as an example: -```{r} +```r library(googledrive) # designate project-specific cache @@ -414,7 +405,7 @@ Do this setup once per project. Another way to accomplish the same setup is to specify the desired cache location directly in the call to the auth function: -```{r} +```r library(googledrive) # trigger auth on purpose --> store a token in the specified cache @@ -423,7 +414,7 @@ drive_auth(cache = ".secrets") **Step 2**: In all downstream use, announce the location of the cache and pre-authorize the use of a suitable token discovered there. Continuing the googledrive example: -```{r} +```r library(googledrive) options( @@ -442,7 +433,7 @@ Depending on the context, it might be suitable to accomplish this in a startup f Here's a variation where we say which token to use by explicitly specifying the associated email. This is handy if there's a reason to have more than one token in the cache. -```{r} +```r library(googledrive) options( @@ -456,7 +447,7 @@ drive_find(n_max = 5) Here's another variation where we specify the necessary info directly in an auth call, instead of in options: -```{r} +```r library(googledrive) drive_auth(cache = ".secrets", email = TRUE) @@ -467,7 +458,7 @@ drive_find(n_max = 5) Here's one last variation that's applicable when the local cache could contain multiple tokens: -```{r} +```r library(googledrive) drive_auth(cache = ".secrets", email = "jenny@example.com") @@ -481,7 +472,7 @@ Personally I would use `here::here(".secrets)"` everywhere above, to make things For troubleshooting purposes, you can set a gargle option to see verbose output about the execution of `gargle::token_fetch()`: -```{r} +```r options(gargle_verbosity = "debug") ``` diff --git a/vignettes/oauth-client-not-app.Rmd b/vignettes/oauth-client-not-app.Rmd index 60f1cac7..88be61da 100644 --- a/vignettes/oauth-client-not-app.Rmd +++ b/vignettes/oauth-client-not-app.Rmd @@ -42,16 +42,16 @@ This flow is triggered when `use_oob = TRUE` (an existing convention in gargle a border-collapse: collapse; border-style: none; } - + .empty-cell { border: none; background-color: transparent; } - + table thead { background-color: transparent; } - + table th, table td { border-style: solid; } @@ -151,7 +151,7 @@ Here are the changes you need to know about in `AuthState`: favor of the new `client` argument. If you call `init_AuthState(app = x)`, there will be a deprecation message and the input `x` is used as the `client` argument instead. - + Here are the changes you probably need to make in your package: * The first argument of the user-facing function, `PKG_auth_configure()`, should @@ -163,7 +163,7 @@ Here are the changes you probably need to make in your package: Here's how `googledrive::drive_auth_configure()` and `googledrive::drive_oauth_client()` looked before and after the transition: -```{r, eval = FALSE} +```r # BEFORE drive_auth_configure <- function(app, path, api_key) { # not showing this code @@ -182,8 +182,8 @@ drive_auth_configure <- function(client, path, api_key, app = deprecated()) { "drive_auth_configure(client)" ) drive_auth_configure(client = app, path = path, api_key = api_key) - } - + } + # not showing this code .auth$set_client(client) # more code we're not showing @@ -202,7 +202,7 @@ drive_oauth_app <- function() { The approach above follows various conventions explained in `vignette("communicate", package = "lifecycle")`. If you also choose to use the lifecycle package to assist in this process, `usethis::use_lifecycle()` function does some helpful one-time setup in your package: -```{r eval = FALSE} +```r usethis::use_lifecycle() ``` @@ -227,8 +227,8 @@ Here are the changes you probably need to make in your package: `app` nor `client` are formal arguments of `gargle::token_fetch()`, instead, these are intended for eventual use by `gargle::credentials_user_oauth2()`. Here's a sketch of how this looks in `googledrive::drive_auth()`: - - ```{r eval = FALSE} + + ```r drive_auth <- function(...) { # code not shown cred <- gargle::token_fetch( @@ -245,6 +245,6 @@ Here are the changes you probably need to make in your package: # code not shown } ``` - + * If you ever call `gargle::credentials_user_oauth2()` directly, use the new `client` argument instead of the deprecated `app` argument. diff --git a/vignettes/request-helper-functions.Rmd b/vignettes/request-helper-functions.Rmd index 7b85a7a1..b0720def 100644 --- a/vignettes/request-helper-functions.Rmd +++ b/vignettes/request-helper-functions.Rmd @@ -19,7 +19,7 @@ This vignette explains the purpose and usage of: * `request_develop(endpoint, params, base_url)` * `request_build(method, path, params, body, token, key, base_url)` * `request_make(x, ..., user_agent)` - + The target audience is someone writing an R package to wrap a Google API. ```{r setup} @@ -40,7 +40,7 @@ The request helpers in gargle check the combined inputs from user and developer * If unrecognized parameters are submitted, an error is thrown. * Parameters are automatically placed in their correct location: URL substitution, query, or body. * *Is there something else you care about? It is possible to do more, but it would help to have concrete requests.* - + Google provides [API libraries for several languages](https://developers.google.com/api-client-library/), including Java, Go, Python, JavaScript, Ruby and more (but not R). All of these libraries are machine-generated from the metadata provided by the API Discovery Service. It is the [official recommendation](https://developers.google.com/discovery/v1/using#build) to use the Discovery Document when building client libraries. The gargle package aims to implement key parts of this strategy, in a way that is also idiomatic for R and its developers. ## High-level design pattern @@ -51,7 +51,7 @@ gargle facilitates this design for API-wrapping packages: - Your package exports thin wrapper functions around gargle's helpers to form and make HTTP requests, that inject package-specific logic and data, such as an API key and user agent. This is for power users and yourself. * High-level, task-oriented, user-facing functions that constitute the main interface of your package. - These functions convert user input into the form required by the API and pass it along to your low-level interface functions. - + Later, specific examples are given, using the googledrive package. ## gargle's HTTP request helpers @@ -66,7 +66,7 @@ gargle provides support for creating and sending HTTP requests via these functio * Peels off `params` destined for the body into their own part. * Returns request data in a form that anticipates the `httr::VERB()` call that is on the horizon. - + `request_build(method, path, params, body, token, key, base_url)`: a.k.a. The Dumb One. * Typically consumes the output of `request_develop()`, although that is not @@ -83,9 +83,9 @@ gargle provides support for creating and sending HTTP requests via these functio request, you would probably just make the `httr::VERB()` call yourself. * Consults `x$method` to determine which `httr::VERB()` to call, then calls it with the rest of `x`, `...`, and `user_agent` passed as arguments. - + They are usually called in the above order, though they don't have to be used that way. It is also fine to ignore this part of gargle and use it only for help with auth. They are separate parts of the package. - + ## Discovery Documents Google's [API Discovery Service](https://developers.google.com/discovery/) "provides a lightweight, JSON-based API that exposes machine-readable metadata about Google APIs". We recommend ingesting this metadata into an R list, stored as internal data in an API-wrapping client package. Then, HTTP requests inside high-level functions can be made concisely and safely, by referring to this metadata. The combined use of this data structure and gargle's request helpers can eliminate a lot of boilerplate data and logic that are shared across Google APIs and across endpoints within an API. @@ -101,9 +101,9 @@ Main files of interest to the developer of a client package: * `ingest-functions.R` is a collection of functions for downloading and ingesting a Discovery Document. * `drive-example.R` uses those functions to ingest metadata on the Drive v3 API and store it as an internal data object for use in [googledrive](https://googledrive.tidyverse.org). - + The remaining files present an analysis of the Discovery Document for the Discovery API itself (very meta!) and write files that are useful for reference. Several are included at the end of this vignette. - + Why aren't the ingest functions exported by gargle? First, we regard this as functionality that is needed at development time, not install or run time. This is something you'll do every few months, probably associated with preparing a release of a wrapper package. Second, the packages that are useful for wrangling JSON and lists are not existing dependencies of gargle, so putting these function in gargle would require some unappealing compromises. ## Method (or endpoint) data @@ -123,7 +123,7 @@ sheets.spreadsheets.sheets.copyTo Retrieve the metadata for one endpoint by name, e.g.: -```{r, eval = FALSE} +```r .endpoints[["drive.files.create"]] ``` @@ -133,7 +133,7 @@ That info can be passed along to `request_develop(endpoint, params, base_url)`, Here's the model used in googledrive. There is a low-level request helper, `googledrive::request_generate()`, that is used to form every request in the package. It is exported as part of a low-level API for expert use, but most users will never know it exists. -```{r eval = FALSE} +```r # googledrive:: request_generate <- function(endpoint = character(), params = list(), @@ -178,7 +178,7 @@ The output of `gargle::request_build()` specifies an HTTP request. `gargle::request_make()` can be used to actually execute it. -```{r, eval = FALSE} +```r # gargle:: request_make <- function(x, ..., user_agent = gargle_user_agent()) { stopifnot(is.character(x$method)) @@ -205,7 +205,7 @@ request_make <- function(x, ..., user_agent = gargle_user_agent()) { In googledrive we have a thin wrapper around this that injects the googledrive user agent: -```{r, eval = FALSE} +```r # googledrive:: request_make <- function(x, ...) { gargle::request_make(x, ..., user_agent = drive_ua()) diff --git a/vignettes/troubleshooting.Rmd b/vignettes/troubleshooting.Rmd index 75dcff56..27740146 100644 --- a/vignettes/troubleshooting.Rmd +++ b/vignettes/troubleshooting.Rmd @@ -75,12 +75,12 @@ If you are using (or struggling to use) a service account token, workload identi Here is indicative output of `gargle_oauth_sitrep()`, for someone who has accepted the default OAuth cache location and has played with several APIs via gargle-using packages. -```{r, eval = FALSE} +```r gargle_oauth_sitrep() #' > 14 tokens found in this gargle OAuth cache: #' '~/Library/Caches/gargle' -#' -#' email app scope hash... +#' +#' email app scope hash... #' ----------------------------- ----------- ------------------------------ ---------- #' abcdefghijklm@gmail.com thingy ...bigquery, ...cloud-platform 128f9cc... #' buzzy@example.org gargle-demo 15acf95... @@ -140,7 +140,7 @@ Anyone relying on the default client will have to upgrade.* The solution is to update the package in question, e.g. googlesheets4: -```{r eval = FALSE} +```r install.packages("googlesheets4") ``` @@ -192,7 +192,7 @@ Unable to refresh token, because the associated OAuth app has been deleted ``` ## How to avoid auth pain - + If you have rigged some remote mission critical thing (e.g. a Shiny app or cron job) to use a cached user OAuth token, one day, one of the problems described above will happen and your mission critical token will stop working. Your thing (e.g. the Shiny app or cron job) will mysteriously fail because the OAuth token can't be refreshed and a new token can't be obtained in a non-interactive setting. This is why cached user tokens are a poor fit for such applications. From 740158248f5ed2ea550833ce85c5f4222e262f2f Mon Sep 17 00:00:00 2001 From: Tan Ho Date: Fri, 19 Sep 2025 15:31:51 -0400 Subject: [PATCH 2/5] avoid butchering backticks --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 6dfab866..9a7308b1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ # gargle (development version) -* In vignettes, convert all ````{r eval=FALSE}` chunks to plain ````r` chunks to prevent any chance of vignette code evaluation while maintaining R syntax highlighting (#301, @tanho63) +* In vignettes, convert all `{r eval=FALSE}` chunks to plain `r` chunks to prevent any chance of vignette code evaluation while maintaining R syntax highlighting (#301, @tanho63) # gargle 1.6.0 From 185494523e4f5d63a6d54865404a97de06b5800d Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Mon, 3 Nov 2025 10:21:23 -0800 Subject: [PATCH 3/5] Let's focus only on the single, troublesome vignette --- vignettes/articles/google-compute-engine.Rmd | 53 ++++++++++--------- .../articles/managing-tokens-securely.Rmd | 18 +++---- vignettes/auth-from-web.Rmd | 6 +-- vignettes/gargle-auth-in-client-package.Rmd | 34 ++++++------ vignettes/get-api-credentials.Rmd | 6 +-- vignettes/how-gargle-gets-tokens.Rmd | 28 +++++----- vignettes/oauth-client-not-app.Rmd | 22 ++++---- vignettes/request-helper-functions.Rmd | 24 ++++----- vignettes/troubleshooting.Rmd | 10 ++-- 9 files changed, 101 insertions(+), 100 deletions(-) diff --git a/vignettes/articles/google-compute-engine.Rmd b/vignettes/articles/google-compute-engine.Rmd index 645538c1..2a6fbeea 100644 --- a/vignettes/articles/google-compute-engine.Rmd +++ b/vignettes/articles/google-compute-engine.Rmd @@ -5,7 +5,8 @@ title: "Google Compute Engine" ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, - comment = "#>" + comment = "#>", + eval = FALSE ) ``` @@ -35,7 +36,7 @@ I have done the required setup for this package: Having done this setup, this is how attaching the package looks: -```r +```{r} library(googleComputeEngineR) #> ✔ Setting scopes to https://www.googleapis.com/auth/cloud-platform #> ✔ Successfully auto-authenticated via /path/to/that/json/mentioned/above.json @@ -49,7 +50,7 @@ I don't think this has any direct connection to instance scopes for VMs created You can see your current instances with `gce_list_instances()`: -```r +```{r} gce_list_instances() #> ==Google Compute Engine Instance List== #> name machineType status zone externalIP creationTimestamp @@ -65,7 +66,7 @@ The above reflects how things look after I've been mucking around a bit and have Here's my basic way of creating a VM: -```r +```{r} vm <- gce_vm( template = "rstudio", name = "cerebral-lion", @@ -79,22 +80,22 @@ I can no longer remember why I settled on `predefined_type = "e2-standard-4"`. Here's what you'll see: -```r +```{r} #> ── ## VM Template: ' rstudio' running at http://{IP_ADDRESS} ───────────────────────────────────────────────────── #> ℹ 2023-04-13 12:03:05 > On first boot, wait a few minutes for docker container to install before logging in. #> ==Google Compute Engine Instance== -#> +#> #> Name: cerebral-lion #> Created: 2023-04-13 12:02:44 #> Machine Type: e2-standard-4 #> Status: RUNNING #> Zone: us-west1-a #> External IP: {IP_ADDRESS} -#> Disks: +#> Disks: #> deviceName type mode boot autoDelete #> 1 cerebral-lion-boot-disk PERSISTENT READ_WRITE TRUE TRUE -#> -#> Metadata: +#> +#> Metadata: #> key value #> 2 template rstudio #> 3 google-logging-enabled true @@ -106,7 +107,7 @@ Here's what you'll see: You can then log in to RStudio Server at the given `{IP_ADDRESS}`. Helpful snippets for getting that on the clipboard: -```r +```{r} # if you, e.g., just created `vm` paste0("http://", gce_get_external_ip(vm)) |> clipr::write_clip() @@ -119,7 +120,7 @@ paste0("http://", gce_get_external_ip("cerebral-lion")) |> Under the hood, googleComputeEngineR is inserting its own default choices for the associated service account and scopes. It's actually as if you had done: -```r +```{r} gce_vm( ..., serviceAccounts = list( @@ -138,21 +139,21 @@ To learn more: name email aliases #> 1 {EMAIL_OF_THE_DEFAULT_SERVICE_ACCOUNT} {EMAIL_OF_THE_DEFAULT_SERVICE_ACCOUNT} default @@ -168,7 +169,7 @@ So there will only ever be 1 actual service account identify, but you might see Let's get a token with `token_fetch()` and inspect it. -```r +```{r} t <- token_fetch() #> trying `token_fetch()` #> ... @@ -179,7 +180,7 @@ t <- token_fetch() #> GCE service account name: "default" #> GCE access token scopes: "...cloud-platform" t -#> +#> #> ── ────────────────────────────────────────────────────────────────────────────────────────────────────── #> scopes: ...cloud-platform #> credentials: access_token, expires_in, token_type @@ -189,7 +190,7 @@ By default, `credentials_gce()` uses the `default` service account and the `"clo What if we want to do something with the Google Drive API and we request that scope? -```r +```{r} t <- token_fetch(c( "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/drive" @@ -211,7 +212,7 @@ t <- token_fetch(c( #> GCE service account name: "default" #> GCE access token scopes: "...cloud-platform" t -#> +#> #> ── ────────────────────────────────────────────────────────────────────────────────────────────────────── #> scopes: ...cloud-platform #> credentials: access_token, expires_in, token_type @@ -223,7 +224,7 @@ We get a token, but still with only the `"cloud-platform"` scope, because the Dr And, indeed, this lack of an explicit Drive scope means that, e.g., the googledrive package can't do operations that require auth: -```r +```{r} library(googledrive) drive_find() #> attempt to access internal gargle data from: googledrive @@ -251,7 +252,7 @@ If you're not actively working on the VM, you should at least suspend it. Then you could resume it to pick up where you left off. To ensure that you aren't incurring any charges, you should stop the machine, but then you'll have to start over if you've, e.g., installed dev packages or downloaded/created any files. -```r +```{r} gce_vm_suspend("cerebral-lion") gce_vm_resume("cerebral-lion") gce_vm_stop("cerebral-lion") @@ -260,7 +261,7 @@ gce_vm_stop("cerebral-lion") It's a good idea to check that you've done whatever you intended with the instance. Check its status here: -```r +```{r} gce_list_instances() #> ==Google Compute Engine Instance List== #> name machineType status zone externalIP creationTimestamp @@ -278,7 +279,7 @@ AFAICT googleComputeEngineR only helps you set scopes at the time of VM creation It seems possible to change scopes for pre-existing instance as long as it is stopped, so maybe that could be a feature request for googleComputeEngineR (or maybe I'm overlooking that there's already a way to do this). Further reading: . -```r +```{r} vm <- gce_vm( template = "rstudio", name = "trustful-bull", @@ -300,7 +301,7 @@ vm <- gce_vm( I get the IP address, log in to RStudio Server, and install the desired version of gargle (not shown). `gce_instance_service_accounts()` shows that we have, in fact, managed to change the `scopes` available to the default service account: -```r +```{r} gce_instance_service_accounts() #> name email aliases #> 1 {EMAIL_OF_THE_DEFAULT_SERVICE_ACCOUNT} {EMAIL_OF_THE_DEFAULT_SERVICE_ACCOUNT} default @@ -313,7 +314,7 @@ gce_instance_service_accounts() We see this in actual tokens as well. Note that we get `"drive"` scope *even if we don't ask for it*. -```r +```{r} t <- token_fetch() #> trying `token_fetch()` #> ... @@ -327,7 +328,7 @@ t <- token_fetch() #> GCE service account name: "default" #> GCE access token scopes: "...cloud-platform, ...drive" t -#> +#> #> ── ────────────────────────────────────────────────────────────────────────────────────────────────────── #> scopes: ...cloud-platform, ...drive #> credentials: access_token, expires_in, token_type @@ -335,7 +336,7 @@ t And, as one would expect, it's now possible to work with the googledrive package. -```r +```{r} library(googledrive) drive_find() #> # A dribble: 0 × 3 diff --git a/vignettes/articles/managing-tokens-securely.Rmd b/vignettes/articles/managing-tokens-securely.Rmd index 188fc4ba..3fb47406 100644 --- a/vignettes/articles/managing-tokens-securely.Rmd +++ b/vignettes/articles/managing-tokens-securely.Rmd @@ -122,7 +122,7 @@ You will be interested in `secret_encrypt_json()` if you want to encrypt a servi `secret_encrypt_json()` takes 3 arguments: * `json`: probably the path to a JSON file, but a JSON string is also - acceptable. + acceptable. * `path`: The path to write the encrypted JSON to. Technically this is optional, but this function mostly exists to write to file. * `key`: The name of the environment variable that holds the encryption key. @@ -130,7 +130,7 @@ You will be interested in `secret_encrypt_json()` if you want to encrypt a servi This example shows how googledrive's testing credentials are placed inside the package source. `googledrive-testing.json` is a JSON file downloaded for a service account managed via the [Google API / Cloud Platform console](https://console.cloud.google.com/project): -```r +```{r eval = FALSE} secret_encrypt_json( json = "~/some/place/where/I/keep/secret/stuff/googledrive-testing.json", path = "inst/secret/googledrive-testing.json", @@ -163,7 +163,7 @@ If at all possible, use a service account instead. This example shows how an encrypted googlesheets4 user token could be placed inside the `.secrets/` directory of a project, e.g. a Shiny app intended for deployment. -```r +```{r eval = FALSE} library(googlesheets4) dir.create(".secrets") @@ -227,7 +227,7 @@ It should come as no surprise that `secret_encrypt_json()` and `secret_write_rds Recall that in the example above we encrypted the JSON specifying a service account token, for use in CI by googledrive. Here's how you would use `secret_decrypt_json()` to decrypt that token and direct googledrive to use it: -```r +```{r eval = FALSE} library(googledrive) drive_auth( @@ -243,7 +243,7 @@ drive_auth( Recall that in the example above we encrypted a googlesheets4 user token, for use inside something like a deployed Shiny app. Here's how you would use `secret_read_rds()` to decrypt that token and direct googlesheets4 to use it: -```r +```{r eval = FALSE} library(googlesheets4) gs4_auth(token = gargle::secret_read_rds( @@ -268,22 +268,22 @@ You do want to rig things for graceful, informative failure in this case. likely to benefit from this is you, i.e. when you're trying to figure out why your app isn't working. It's nice to have a clear signal that the encryption key is unavailable instead of some mysterious deployment failure. - + #### Condition on key availability `secret_has_key("SOMETHING_KEY")` reports whether the `"SOMETHING_KEY"` environment variable is defined. In a deployed data product, you might want to call `secret_has_key()` before any attempt to decrypt a secret. If the encryption key is not available, report that finding and arrange to do something graceful instead of erroring, especially in some cryptic, difficult-to-debug way. - + #### Automatic skips The `secret_*` functions have a built-in feature such that, if they are called during testing, when the encryption key is unavailable, that test is skipped. That behaviour is implemented in the internal helper `secret_get_key()`, which looks something like this: -```r +```{r eval = FALSE} secret_get_key <- function(envvar) { key <- Sys.getenv(envvar) - + if (identical(key, "")) { if (is_testing()) { msg <- glue("Env var {envvar} not defined.") diff --git a/vignettes/auth-from-web.Rmd b/vignettes/auth-from-web.Rmd index 24ba94f4..5d51caa2 100644 --- a/vignettes/auth-from-web.Rmd +++ b/vignettes/auth-from-web.Rmd @@ -98,7 +98,7 @@ Packages like googledrive and bigrquery aim to make auth "just work" for most us However, it is always possible to initiate auth yourself, which gives you the opportunity to specify non-default values of certain parameters. Here's how you could request OOB auth, using googledrive as an example: -```r +```{r eval = FALSE} library(googledrive) drive_auth(use_oob = TRUE) @@ -111,7 +111,7 @@ drive_find(n_max = 5) If you know that you *always* want to use OOB, as a user or within a project, the best way to express this is to set the `"gargle_oob_default"` option. -```r +```{r eval = FALSE} options(gargle_oob_default = TRUE) ``` @@ -187,7 +187,7 @@ If we are using OOB auth, the decision between conventional or pseudo-OOB is mad Packages that use a built-in tidyverse OAuth client (googledrive, googlesheets4, and bigrquery) should automatically select a "web" client on RStudio Server, Posit Cloud, Posit Workbench, and Google Colaboratory and an "installed" client otherwise. If you need to explicitly request a "web" client in some other setting, you can use the global option `"gargle_oauth_client_type"`: -```r +```{r eval = FALSE} options(gargle_oauth_client_type = "web") ``` diff --git a/vignettes/gargle-auth-in-client-package.Rmd b/vignettes/gargle-auth-in-client-package.Rmd index 77160d42..125e552a 100644 --- a/vignettes/gargle-auth-in-client-package.Rmd +++ b/vignettes/gargle-auth-in-client-package.Rmd @@ -26,13 +26,13 @@ Getting a token requires several pieces of information and there are stark diffe * Overall config: OAuth client and API key. Who provides? * Token-level properties: Google identity (email) and scopes. * Request-level: Who manages tokens and injects them into requests? - + ### User-facing auth In googledrive, the main user-facing auth function is `googledrive::drive_auth()`. Here is its definition (at least approximately, remember this is static code): -```r +```{r, eval = FALSE} # googledrive:: drive_auth <- function(email = gargle::gargle_oauth_email(), path = NULL, @@ -74,19 +74,19 @@ The internal `.auth` object maintains googledrive's auth state and is explained A client package can use an internal object of class `gargle::AuthState` to hold the auth state. In googledrive, the main auth file defines a placeholder `.auth` object: -```r +```{r eval = FALSE} .auth <- NULL ``` The actual initialization happens in `.onLoad()`: -```r +```{r} .onLoad <- function(libname, pkgname) { utils::assignInMyNamespace( ".auth", gargle::init_AuthState(package = "googledrive", auth_active = TRUE) ) - + # other stuff } ``` @@ -108,7 +108,7 @@ The client is a component that most users do not even know about and they are co There is a field in the `.auth` auth state to hold the OAuth `client`. Exported auth helpers, `drive_oauth_client()` and `drive_auth_configure()`, retrieve and modify the current client to support users who want to (or must) take that level of control. -```r +```{r, eval = FALSE} library(googledrive) # first: download the OAuth client as a JSON file @@ -140,7 +140,7 @@ For example, this is a great way to read a Google Sheet that is world-readable o The user can provide their own API key via `drive_auth_configure(api_key =)` and retrieve that value with `drive_api_key()`, just as with the OAuth client. The API key is stored in the `api_key` field of the `.auth` auth state. -```r +```{r, eval = FALSE} library(googledrive) drive_auth_configure(api_key = "123456789") @@ -164,7 +164,7 @@ Since users may have more than one Google account, it's quite likely that they w That explains why `drive_auth()` has the optional `email` argument that lets users proactively specify their identity. `drive_auth()` is usually called indirectly upon first need, but a user can also call it proactively in order to specify their target `email`: -```r +```{r eval = FALSE} # googledrive:: drive_auth(email = "janedoe_work@gmail.com") ``` @@ -181,7 +181,7 @@ A client package can usually pick sensible default scopes, that will support wha Here's a reminder of the signature of `googledrive::drive_auth()`: -```r +```{r, eval = FALSE} # googledrive:: drive_auth <- function(email = gargle::gargle_oauth_email(), path = NULL, @@ -194,7 +194,7 @@ drive_auth <- function(email = gargle::gargle_oauth_email(), googledrive ships with a default scope, but a motivated user could call `drive_auth()` preemptively at the start of the session and request different scopes. For example, if they intend to only read data and want to guard against inadvertent file modification, they might opt for the `drive.readonly` scope. -```r +```{r, eval = FALSE} # googledrive:: drive_auth(scopes = "https://www.googleapis.com/auth/drive.readonly") ``` @@ -230,7 +230,7 @@ I focus on early use, by the naive user, with the OAuth flow. When the user first calls a high-level googledrive function such as `drive_find()`, a Drive request is ultimately generated with a call to `googledrive::request_generate()`. Here is its definition, at least approximately: -```r +```{r eval = FALSE} # googledrive:: request_generate <- function(endpoint = character(), params = list(), @@ -260,11 +260,11 @@ request_generate <- function(endpoint = character(), ``` `googledrive::request_generate()` is a thin wrapper around `gargle::request_develop()` and `gargle::request_build()` that only implements details specific to googledrive, before delegating to more general functions in gargle. -The `vignette("request-helper-functions")` documents these gargle functions. +The `vignette("request-helper-functions")` documents these gargle functions. `googledrive::request_generate()` gets a token with `drive_token()`, which is defined like so: -```r +```{r eval = FALSE} # googledrive:: drive_token <- function() { if (isFALSE(.auth$auth_active)) { @@ -279,7 +279,7 @@ drive_token <- function() { where `drive_has_token()` in a helper defined as: -```r +```{r eval = FALSE} # googledrive:: drive_has_token <- function() { inherits(.auth$cred, "Token2.0") @@ -297,7 +297,7 @@ Multiple gargle-using packages can use a shared token by obtaining a suitably sc For example, the default scope requested by googledrive is also sufficient for operations available in googlesheets4. You could use a shared token like so: -```r +```{r eval = FALSE} library(googledrive) library(googlesheets4) @@ -310,7 +310,7 @@ gs4_auth(token = drive_token()) # registers token with googlesheets4 ``` It is important to make sure that the token-requesting package (googledrive, above) is using an OAuth client for which all the necessary APIs and scopes are enabled. - + ## Auth interface The exported functions like `drive_auth()`, `drive_token()`, etc. constitute the auth interface between googledrive and gargle and are centralized in [`tidyverse/googledrive/R/drive_auth.R`](https://github.com/tidyverse/googledrive/blob/main/R/drive_auth.R). @@ -369,7 +369,7 @@ One reason for a user to call `drive_auth()` directly and proactively is to swit `drive_auth()` accepts an `email` argument, which is honored when gargle determines if there is already a suitable token on hand. Here is a sketch of how a user could switch identities during a session, possibly non-interactive: -```r +```{r eval = FALSE} library(googledrive) drive_auth(email = "janedoe_work@gmail.com") diff --git a/vignettes/get-api-credentials.Rmd b/vignettes/get-api-credentials.Rmd index b62ac777..43b41031 100644 --- a/vignettes/get-api-credentials.Rmd +++ b/vignettes/get-api-credentials.Rmd @@ -78,7 +78,7 @@ Package maintainers might want to build an API key in as a fallback, possibly ta Package users could register an API key for use with a wrapper package. For example, in googlesheets4, one would use `googlesheets4::gs4_auth_configure()` to store a key for use in downstream requests, i.e. after a call to `googlesheets4::gs4_deauth()`: -```r +```{r eval = FALSE} library(googlesheets4) gs4_auth_configure(api_key = "YOUR_API_KEY_GOES_HERE") @@ -126,7 +126,7 @@ Package maintainers might want to build this client in as a fallback, possibly t Package users could register their own client for use with a wrapper package. For example, in googledrive, one would use `googledrive::drive_auth_configure()` to do this: -```r +```{r, eval = FALSE} library(googledrive) google_client <- gargle::gargle_oauth_client_from_json( @@ -188,7 +188,7 @@ Authors of wrapper packages can use the symmetric encryption strategy described You could provide the token's filepath to a wrapper package's main auth function, e.g.: -```r +```{r eval = FALSE} # googledrive drive_auth(path = "/path/to/your/service-account-token.json") ``` diff --git a/vignettes/how-gargle-gets-tokens.Rmd b/vignettes/how-gargle-gets-tokens.Rmd index 9c2bb339..704e1c15 100644 --- a/vignettes/how-gargle-gets-tokens.Rmd +++ b/vignettes/how-gargle-gets-tokens.Rmd @@ -45,7 +45,7 @@ The objective of `token_fetch()` is to allow package developers to take responsi The signature of `token_fetch()` is very simple and, therefore, not very informative: -```r +```{r, eval = FALSE} token_fetch(scopes, ...) ``` @@ -75,7 +75,7 @@ Read more in the docs for `gargle_verbosity()`. The first function tried is `credentials_byo_oauth2()`. Here's how a call to `token_fetch()` might work: -```r +```{r, eval = FALSE} token_fetch(token = ) credentials_byo_oauth2( @@ -97,7 +97,7 @@ If `token` is not provided or if it doesn't satisfy these requirements, we fail The next function tried is `credentials_service_account()`. Here's how a call to `token_fetch()` with service account inputs plays out: -```r +```{r, eval = FALSE} token_fetch(scopes = , path = "/path/to/your/service-account.json") # credentials_byo_oauth2() fails because no `token`, @@ -135,7 +135,7 @@ If your service account token requests fail with "Bad Request" inside a containe The next function tried is `credentials_external_account()`. Here's how a call to `token_fetch()` with an external account inputs plays out: -```r +```{r, eval = FALSE} token_fetch(scopes = , path = "/path/to/your/external-account.json") # credentials_byo_oauth2() fails because no `token`, @@ -168,7 +168,7 @@ Here is some Google documentation about workload identity federation and the spe The next function tried is `credentials_app_default()`. Here's how a call to `token_fetch()` might work: -```r +```{r, eval = FALSE} token_fetch(scopes = ) # credentials_byo_oauth2() fails because no `token`, @@ -186,7 +186,7 @@ The credentials themselves are conventional service account, external account, o The hope is to make auth "just work" for someone working on Google-provided infrastructure or who has used Google tooling to get started, such as the [`gcloud` command line tool](https://cloud.google.com/sdk/gcloud). A sequence of paths is consulted, which we describe here, with some abuse of notation. ALL_CAPS represents the value of an environment variable. -```r +```{r, eval = FALSE} ${GOOGLE_APPLICATION_CREDENTIALS} ${CLOUDSDK_CONFIG}/application_default_credentials.json @@ -220,7 +220,7 @@ The JSON configuration for an external account is not actually sensitive and thi The next function tried is `credentials_gce()`. Here's how a call to `token_fetch()` might work: -```r +```{r, eval = FALSE} token_fetch(scopes = ) # or perhaps token_fetch(scopes = , service_account = ) @@ -251,7 +251,7 @@ If this seems to happening to you and it's not what you want, see the last secti The next and final function tried is `credentials_user_oauth2()`. Here's how a call to `token_fetch()` might work: -```r +```{r, eval = FALSE} token_fetch(scopes = ) # credentials_byo_oauth2() fails because no `token`, @@ -279,7 +279,7 @@ Per the Google User Data Policy 14 tokens found in this gargle OAuth cache: #> ~/Library/Caches/gargle @@ -388,7 +388,7 @@ Let's say you want to prevent `token_fetch()` from even trying one specific auth You can remove a specific credential function from the registry. Here's how to do this for the scenario described above, where you want to skip GCE-specific auth: -```r +```{r eval = FALSE} gargle::cred_funs_add(credentials_gce = NULL) ``` diff --git a/vignettes/oauth-client-not-app.Rmd b/vignettes/oauth-client-not-app.Rmd index 88be61da..60f1cac7 100644 --- a/vignettes/oauth-client-not-app.Rmd +++ b/vignettes/oauth-client-not-app.Rmd @@ -42,16 +42,16 @@ This flow is triggered when `use_oob = TRUE` (an existing convention in gargle a border-collapse: collapse; border-style: none; } - + .empty-cell { border: none; background-color: transparent; } - + table thead { background-color: transparent; } - + table th, table td { border-style: solid; } @@ -151,7 +151,7 @@ Here are the changes you need to know about in `AuthState`: favor of the new `client` argument. If you call `init_AuthState(app = x)`, there will be a deprecation message and the input `x` is used as the `client` argument instead. - + Here are the changes you probably need to make in your package: * The first argument of the user-facing function, `PKG_auth_configure()`, should @@ -163,7 +163,7 @@ Here are the changes you probably need to make in your package: Here's how `googledrive::drive_auth_configure()` and `googledrive::drive_oauth_client()` looked before and after the transition: -```r +```{r, eval = FALSE} # BEFORE drive_auth_configure <- function(app, path, api_key) { # not showing this code @@ -182,8 +182,8 @@ drive_auth_configure <- function(client, path, api_key, app = deprecated()) { "drive_auth_configure(client)" ) drive_auth_configure(client = app, path = path, api_key = api_key) - } - + } + # not showing this code .auth$set_client(client) # more code we're not showing @@ -202,7 +202,7 @@ drive_oauth_app <- function() { The approach above follows various conventions explained in `vignette("communicate", package = "lifecycle")`. If you also choose to use the lifecycle package to assist in this process, `usethis::use_lifecycle()` function does some helpful one-time setup in your package: -```r +```{r eval = FALSE} usethis::use_lifecycle() ``` @@ -227,8 +227,8 @@ Here are the changes you probably need to make in your package: `app` nor `client` are formal arguments of `gargle::token_fetch()`, instead, these are intended for eventual use by `gargle::credentials_user_oauth2()`. Here's a sketch of how this looks in `googledrive::drive_auth()`: - - ```r + + ```{r eval = FALSE} drive_auth <- function(...) { # code not shown cred <- gargle::token_fetch( @@ -245,6 +245,6 @@ Here are the changes you probably need to make in your package: # code not shown } ``` - + * If you ever call `gargle::credentials_user_oauth2()` directly, use the new `client` argument instead of the deprecated `app` argument. diff --git a/vignettes/request-helper-functions.Rmd b/vignettes/request-helper-functions.Rmd index b0720def..7b85a7a1 100644 --- a/vignettes/request-helper-functions.Rmd +++ b/vignettes/request-helper-functions.Rmd @@ -19,7 +19,7 @@ This vignette explains the purpose and usage of: * `request_develop(endpoint, params, base_url)` * `request_build(method, path, params, body, token, key, base_url)` * `request_make(x, ..., user_agent)` - + The target audience is someone writing an R package to wrap a Google API. ```{r setup} @@ -40,7 +40,7 @@ The request helpers in gargle check the combined inputs from user and developer * If unrecognized parameters are submitted, an error is thrown. * Parameters are automatically placed in their correct location: URL substitution, query, or body. * *Is there something else you care about? It is possible to do more, but it would help to have concrete requests.* - + Google provides [API libraries for several languages](https://developers.google.com/api-client-library/), including Java, Go, Python, JavaScript, Ruby and more (but not R). All of these libraries are machine-generated from the metadata provided by the API Discovery Service. It is the [official recommendation](https://developers.google.com/discovery/v1/using#build) to use the Discovery Document when building client libraries. The gargle package aims to implement key parts of this strategy, in a way that is also idiomatic for R and its developers. ## High-level design pattern @@ -51,7 +51,7 @@ gargle facilitates this design for API-wrapping packages: - Your package exports thin wrapper functions around gargle's helpers to form and make HTTP requests, that inject package-specific logic and data, such as an API key and user agent. This is for power users and yourself. * High-level, task-oriented, user-facing functions that constitute the main interface of your package. - These functions convert user input into the form required by the API and pass it along to your low-level interface functions. - + Later, specific examples are given, using the googledrive package. ## gargle's HTTP request helpers @@ -66,7 +66,7 @@ gargle provides support for creating and sending HTTP requests via these functio * Peels off `params` destined for the body into their own part. * Returns request data in a form that anticipates the `httr::VERB()` call that is on the horizon. - + `request_build(method, path, params, body, token, key, base_url)`: a.k.a. The Dumb One. * Typically consumes the output of `request_develop()`, although that is not @@ -83,9 +83,9 @@ gargle provides support for creating and sending HTTP requests via these functio request, you would probably just make the `httr::VERB()` call yourself. * Consults `x$method` to determine which `httr::VERB()` to call, then calls it with the rest of `x`, `...`, and `user_agent` passed as arguments. - + They are usually called in the above order, though they don't have to be used that way. It is also fine to ignore this part of gargle and use it only for help with auth. They are separate parts of the package. - + ## Discovery Documents Google's [API Discovery Service](https://developers.google.com/discovery/) "provides a lightweight, JSON-based API that exposes machine-readable metadata about Google APIs". We recommend ingesting this metadata into an R list, stored as internal data in an API-wrapping client package. Then, HTTP requests inside high-level functions can be made concisely and safely, by referring to this metadata. The combined use of this data structure and gargle's request helpers can eliminate a lot of boilerplate data and logic that are shared across Google APIs and across endpoints within an API. @@ -101,9 +101,9 @@ Main files of interest to the developer of a client package: * `ingest-functions.R` is a collection of functions for downloading and ingesting a Discovery Document. * `drive-example.R` uses those functions to ingest metadata on the Drive v3 API and store it as an internal data object for use in [googledrive](https://googledrive.tidyverse.org). - + The remaining files present an analysis of the Discovery Document for the Discovery API itself (very meta!) and write files that are useful for reference. Several are included at the end of this vignette. - + Why aren't the ingest functions exported by gargle? First, we regard this as functionality that is needed at development time, not install or run time. This is something you'll do every few months, probably associated with preparing a release of a wrapper package. Second, the packages that are useful for wrangling JSON and lists are not existing dependencies of gargle, so putting these function in gargle would require some unappealing compromises. ## Method (or endpoint) data @@ -123,7 +123,7 @@ sheets.spreadsheets.sheets.copyTo Retrieve the metadata for one endpoint by name, e.g.: -```r +```{r, eval = FALSE} .endpoints[["drive.files.create"]] ``` @@ -133,7 +133,7 @@ That info can be passed along to `request_develop(endpoint, params, base_url)`, Here's the model used in googledrive. There is a low-level request helper, `googledrive::request_generate()`, that is used to form every request in the package. It is exported as part of a low-level API for expert use, but most users will never know it exists. -```r +```{r eval = FALSE} # googledrive:: request_generate <- function(endpoint = character(), params = list(), @@ -178,7 +178,7 @@ The output of `gargle::request_build()` specifies an HTTP request. `gargle::request_make()` can be used to actually execute it. -```r +```{r, eval = FALSE} # gargle:: request_make <- function(x, ..., user_agent = gargle_user_agent()) { stopifnot(is.character(x$method)) @@ -205,7 +205,7 @@ request_make <- function(x, ..., user_agent = gargle_user_agent()) { In googledrive we have a thin wrapper around this that injects the googledrive user agent: -```r +```{r, eval = FALSE} # googledrive:: request_make <- function(x, ...) { gargle::request_make(x, ..., user_agent = drive_ua()) diff --git a/vignettes/troubleshooting.Rmd b/vignettes/troubleshooting.Rmd index 27740146..75dcff56 100644 --- a/vignettes/troubleshooting.Rmd +++ b/vignettes/troubleshooting.Rmd @@ -75,12 +75,12 @@ If you are using (or struggling to use) a service account token, workload identi Here is indicative output of `gargle_oauth_sitrep()`, for someone who has accepted the default OAuth cache location and has played with several APIs via gargle-using packages. -```r +```{r, eval = FALSE} gargle_oauth_sitrep() #' > 14 tokens found in this gargle OAuth cache: #' '~/Library/Caches/gargle' -#' -#' email app scope hash... +#' +#' email app scope hash... #' ----------------------------- ----------- ------------------------------ ---------- #' abcdefghijklm@gmail.com thingy ...bigquery, ...cloud-platform 128f9cc... #' buzzy@example.org gargle-demo 15acf95... @@ -140,7 +140,7 @@ Anyone relying on the default client will have to upgrade.* The solution is to update the package in question, e.g. googlesheets4: -```r +```{r eval = FALSE} install.packages("googlesheets4") ``` @@ -192,7 +192,7 @@ Unable to refresh token, because the associated OAuth app has been deleted ``` ## How to avoid auth pain - + If you have rigged some remote mission critical thing (e.g. a Shiny app or cron job) to use a cached user OAuth token, one day, one of the problems described above will happen and your mission critical token will stop working. Your thing (e.g. the Shiny app or cron job) will mysteriously fail because the OAuth token can't be refreshed and a new token can't be obtained in a non-interactive setting. This is why cached user tokens are a poor fit for such applications. From c4e1f811a5d942681fa6b4a809125c3b7bbb6069 Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Mon, 3 Nov 2025 10:56:55 -0800 Subject: [PATCH 4/5] Style the way Air would + typo fix --- vignettes/non-interactive-auth.Rmd | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/vignettes/non-interactive-auth.Rmd b/vignettes/non-interactive-auth.Rmd index d9b094d6..2de01536 100644 --- a/vignettes/non-interactive-auth.Rmd +++ b/vignettes/non-interactive-auth.Rmd @@ -94,12 +94,16 @@ This token request can be made for specific scopes and, in general, most wrapper Consider the signature of `googledrive::drive_auth()`: ```r -drive_auth <- function(email = gargle::gargle_oauth_email(), - path = NULL, - scopes = "https://www.googleapis.com/auth/drive", - cache = gargle::gargle_oauth_cache(), - use_oob = gargle::gargle_oob_default(), - token = NULL) { ... } +drive_auth <- function( + email = gargle::gargle_oauth_email(), + path = NULL, + scopes = "https://www.googleapis.com/auth/drive", + cache = gargle::gargle_oauth_cache(), + use_oob = gargle::gargle_oob_default(), + token = NULL +) { + ... +} ``` The googledrive package asks for a token with `"drive"` scope, by default. @@ -171,7 +175,9 @@ Here is example code that you might execute in your Docker container: ```r options(gargle.gce.use_ip = TRUE) -t <- gargle::credentials_gce("my-service-key@my-project.iam.gserviceaccount.com") +t <- gargle::credentials_gce( + "my-service-key@my-project.iam.gserviceaccount.com" +) # ... do authenticated stuff with the token t ... ``` @@ -250,7 +256,7 @@ t <- gargle::credentials_service_account( scopes = ..., subject = "user@example.com" ) -googledrive::dive_auth(token = t) +googledrive::drive_auth(token = t) ``` If delegation of domain-wide authority is impossible or unappealing, you must use an OAuth user token, as described below. From 9b8d71a63c974a64d783d479c86af7b356f0fb6b Mon Sep 17 00:00:00 2001 From: Jenny Bryan Date: Mon, 3 Nov 2025 11:05:19 -0800 Subject: [PATCH 5/5] Revert changes to README as well --- README.Rmd | 6 +++--- README.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.Rmd b/README.Rmd index 824afbf3..aea06984 100644 --- a/README.Rmd +++ b/README.Rmd @@ -46,13 +46,13 @@ See the [articles](https://gargle.r-lib.org/articles/) for holistic advice on ho You can install the released version of gargle from [CRAN](https://CRAN.R-project.org) with: -```r +```{r eval = FALSE} install.packages("gargle") ``` And the development version from [GitHub](https://github.com/) with: -```r +```{r eval = FALSE} # install.packages("pak") pak::pak("r-lib/gargle") ``` @@ -61,7 +61,7 @@ pak::pak("r-lib/gargle") gargle is a low-level package and does not do anything visibly exciting on its own. But here's a bit of usage in an interactive scenario where a user confirms they want to use a specific Google identity and loads an OAuth2 token. -```r +```{r eval = FALSE} library(gargle) token <- token_fetch() diff --git a/README.md b/README.md index 549f222b..c0a6b26c 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ out <- response_process(resp) out <- out[["items"]][1:8] sort(vapply(out, function(x) x[["family"]], character(1))) -#> [1] "Inter" "Lato" "Material Icons" "Montserrat" +#> [1] "Inter" "Lato" "Material Icons" "Montserrat" #> [5] "Noto Sans JP" "Open Sans" "Poppins" "Roboto" ```