Managing secrets and configuration can be a challenge in applications which need to work in different development and deployment environments. Typically, secrets are credentials for an external service such as a database. Environments, on the other hand, are used to manage multiple configurations that we can be easily switched between. Rhino recommends a way for working with either one.

Secrets

Secrets are a confidential information and should not be tracked in your version control system. Therefore, a natural place for them are system environment variables. Variables set in system environment can be retrieved within your code with Sys.getenv().

R provides a way to easily set environment variables. Upon a session start (or restart) R reads .Renviron file contents and sets environment variables.

.Renviron

# A comment in .Renviron file
DATABASE_PASSWORD="foobar123!"
API_KEY="75170fc230cd88f32e475ff4087f81d9"

Secrets defined via environment variables can be read and used the following way:

db_password <- Sys.getenv("DATABASE_PASSWORD")
if (db_password == "") {
  # Handle unset or empty DATABASE_PASSWORD variable
}
pool <- pool::dbPool(
  drv = RMySQL::MySQL(),
  dbname = "...",
  host = "...",
  username = "admin",
  password = db_password
)

Recommendations for storing secrets

  1. Store secrets and environment variables in .Renviron.
  2. Use a separate .Renviron file for every environment. Swap the whole file when changing environments.
  3. Use CONSTANT_CASE for variable names.
  4. Do not track .Renviron file in a version control system. Store it in a secure location, e.g. a password manager.
  5. Do not publish .Renviron to RStudio Connect nor shinyapps.io. Both, RStudio Connect and Shiny Apps, provide means to manage environment variables.

Environments

Having every configurable setting stored as an environment variable would result in overgrown .Renviron files. That's where configurable environments come in.

Everything that is not confidential can be tracked by a version control system. Rhino endorses use of {config} package for managing environments.

config.yml

default:
  rhino_log_level: !expr Sys.getenv("RHINO_LOG_LEVEL", "INFO")
  rhino_log_file: !expr Sys.getenv("RHINO_LOG_FILE", NA)
  database_user: "service_account"
  database_schema: "dev"

dev:
  rhino_log_level: !expr Sys.getenv("RHINO_LOG_LEVEL", "DEBUG")

staging:
  database_schema: "stg"

production:
  database_user: "service_account_prod"
  database_schema: "prod"

.Renviron

R_CONFIG_ACTIVE="dev"

You can access the configuration variables in the following way:

box::use(config)

config$get("rhino_log_level") # == "DEBUG"
config$get("database_user") # == "service_account"

config$get("rhino_log_level", config = "production") # == "INFO"
config$get("database_user", config = "production") # == "service_account_prod"

withr::with_envvar(list(RHINO_LOG_LEVEL = "ERROR"), {
  config$get("rhino_log_level") # == "ERROR"
  config$get("rhino_log_level", config = "production") # == "ERROR"
})

Recommendations for managing environments

  1. Define environments and their settings in config.yml.
  2. Select config by setting R_CONFIG_ACTIVE variable in .Renviron.
  3. Make use of default values.
  4. Use !expr Sys.getenv() to make settings overridable with environment variables.
  5. Import config with box and call as usual, i.e. box::use(config) and config$get().
  6. Use snake_case for field names.


Appsilon/rhino documentation built on Sept. 27, 2024, 7:01 p.m.