Nothing
# nocov start
# TODO: A nicer way to execute these system commands...
# - debug output... better error handling... etc.
determine_license_env <- function(license) {
if (fs::file_exists(license) && fs::path_ext(license) == "lic") {
# Docker needs this to be an absolute path
license <- fs::path_abs(license)
cat_line("determine_license: looks like a license file")
return(list(
type = "file",
cmd_params = c("-v", paste0(license, ":/var/lib/rstudio-connect/license.lic")),
env_params = c(RSC_LICENSE_FILE = license)
))
} else {
cat_line("determine_license: looks like a license key")
return(list(
type = "key",
cmd_params = c(),
env_params = c(
RSC_LICENSE = license
)
))
}
}
version_to_docker_tag <- function(version) {
# Prior to 2022.09.0, the plain version number was the tag
# After, it's "<ubuntu-codename>-<version>"
# If you want a specific image tag, just pass it in and it will go through unchanged
try(
{
numver <- numeric_version(version)
if (numver > "2023.06") {
version <- paste0("jammy-", version)
} else if (numver >= "2022.09") {
# There's both jammy and bionic for these, but the bionic ones
# seem more reliable in CI
version <- paste0("bionic-", version)
}
},
silent = TRUE
)
version
}
compose_start <- function(connect_license = Sys.getenv("RSC_LICENSE"), connect_version, clean = TRUE) {
warn_dire("compose_start")
scoped_dire_silence()
stopifnot(nchar(connect_license) > 0)
license_details <- determine_license_env(connect_license)
compose_file <- switch(license_details$type,
"file" = "ci/test-connect-lic.yml",
"ci/test-connect.yml"
)
compose_file_path <- system.file(compose_file, package = "connectapi")
env_vars <- c(
CONNECT_VERSION = version_to_docker_tag(connect_version),
PATH = Sys.getenv("PATH"),
license_details$env_params
)
# system2 needs a character vector of name=value
env_vars <- paste(names(env_vars), env_vars, sep = "=")
docker <- Sys.which("docker")
# stop compose
if (clean) {
system2(docker, c("compose", "-f", compose_file_path, "down"), env = env_vars)
}
# start compose
cat_line("docker compose: starting...")
args <- c("compose", "-f", compose_file_path, "up", "-d")
# do not show env_vars b/c need to handle license securely
cat_line(glue::glue("command: {docker} {paste(args, collapse=' ')}"))
system2(docker, args, env = env_vars)
cat_line("docker compose: started!")
invisible()
}
compose_find_hosts <- function(prefix) {
warn_dire("compose_find")
scoped_dire_silence()
# get docker containers
cat_line("docker: getting list of containers...")
docker_ps_output <- system2("docker", "ps", stdout = TRUE)
cat(docker_ps_output, sep = "\n")
containers <- grep(prefix, docker_ps_output, value = TRUE)
ports <- sub(".*0\\.0\\.0\\.0:([0-9]+)->3939.*", "\\1", containers)
cat_line(glue::glue("docker: got ports {ports[1]} and {ports[2]}"))
# TODO: make this silly sleep more savvy
cat_line("connect: sleeping - waiting for connect to start")
Sys.sleep(10)
paste0("http://localhost:", ports)
}
update_renviron_creds <- function(server, api_key, prefix, .file = ".Renviron") {
cat_line(glue::glue("connect: writing values for {prefix} to {.file}"))
curr_environ <- tryCatch(readLines(.file), error = function(e) {
print(e)
return(character())
})
curr_environ <- curr_environ[!grepl(glue::glue("^{prefix}_SERVER="), curr_environ)]
curr_environ <- curr_environ[!grepl(glue::glue("^{prefix}_API_KEY="), curr_environ)]
output_environ <- glue::glue(
paste(curr_environ, collapse = "\n"),
"{prefix}_SERVER={server}",
"{prefix}_API_KEY={api_key}",
.sep = "\n"
)
if (!fs::file_exists(.file)) fs::file_touch(.file)
writeLines(output_environ, .file)
invisible()
}
build_test_env <- function(connect_license = Sys.getenv("RSC_LICENSE"),
clean = TRUE,
username = "admin",
password = "admin0",
connect_version = Sys.getenv("CONNECT_VERSION", current_connect_version)) {
warn_dire("build_test_env")
scoped_dire_silence()
compose_start(connect_license = connect_license, clean = clean, connect_version = connect_version)
# It was ci_connect before but it's ci-connect on my machine now;
# this is a regex so it will match either
hosts <- compose_find_hosts(prefix = "ci.connect")
cat_line("connect: creating first admin...")
a1 <- create_first_admin(
hosts[1],
"admin", "admin0", "admin@example.com"
)
a2 <- create_first_admin(
hosts[2],
"admin", "admin0", "admin@example.com"
)
update_renviron_creds(a1$server, a1$api_key, "TEST_1")
update_renviron_creds(a2$server, a2$api_key, "TEST_2")
cat_line("connect: done")
cat_line()
cat_line()
cat_line("NOTE: be sure to reload .Renviron before running tests!")
cat_line(' readRenviron(".Renviron")')
cat_line(' source("tests/test-integrated.R")')
cat_line()
cat_line()
return(
list(
connect1 = a1,
connect2 = a2
)
)
}
# set up the first admin
create_first_admin <- function(url,
user, password, email,
keyname = "first-key",
provider = "password") {
warn_dire("create_first_admin")
check_connect_license(url)
client <- HackyConnect$new(server = url, api_key = NULL)
if (provider == "password") {
tryCatch(
{
first_admin <- client$POST(
path = v1_url("users"),
body = list(
username = user,
password = password,
email = email
)
)
},
error = function(e) {
message(glue::glue("Error creating first admin: {e}"))
}
)
} else if (provider %in% c("ldap", "pam")) {
# can just log in using user / password
} else {
stop("Unsupported authentication provider")
}
login <- client$login(
user = user,
password = password
)
user_info <- client$me()
api_key <- client$POST(
path = unversioned_url("keys"),
body = list(name = keyname)
)
return(
connect(url, api_key$key)
)
}
HackyConnect <- R6::R6Class(
"HackyConnect",
inherit = Connect,
public = list(
xsrf = NULL,
login = function(user, password) {
warn_dire("HackyConnect")
res <- httr::POST(
glue::glue("{self$server}/__login__"),
body = list(username = user, password = password),
encode = "json"
)
res_cookies <- httr::cookies(res)
self$xsrf <- res_cookies[res_cookies$name == "RSC-XSRF", "value"]
httr::content(res, as = "parsed")
},
add_auth = function() {
httr::add_headers("X-RSC-XSRF" = self$xsrf)
}
)
)
# nocov end
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.