Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Type: Package
Package: audit.connect
Title: Posit Connect Health Check
Version: 0.8.7
Version: 0.8.8
Authors@R:
person("Jumping", "Rivers", , "info@jumpingrivers.com", role = c("aut", "cre"))
Description: Posit Connect Health Check. Deploys various content types to
Expand All @@ -25,14 +25,14 @@ Imports:
rlang,
rsconnect (>= 1.10.0),
stringr,
tibble
tibble,
withr
Suggests:
glue,
gt,
gtExtras,
htmltools,
testthat (>= 3.0.0),
withr,
yaml
Remotes:
jumpingrivers/audit.base,
Expand Down
8 changes: 8 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# audit.connect 0.8.8

## Bug fixes and minor improvements

* `report.qmd` renders on licensed servers. The summary stores how much content each locked user owns, not the full content list (#64).
* The Python checks are reported as skipped when rsconnect-python is missing, instead of two failing and two being left out (#65).
* The API key is passed to rsconnect-python through `CONNECT_API_KEY`, not on the command line, and the rsconnect account that holds it is kept in a temporary folder (#66).

# audit.connect 0.8.7

## Bug fixes and minor improvements
Expand Down
2 changes: 2 additions & 0 deletions R/check.R
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#' @export
check = function(server = NULL, token = NULL, dir = ".", debug_level = 0:2) {
debug_level = get_debug_level(force(debug_level))
# rsconnect saves the account, API key included, in its config folder; use a temporary one
withr::local_envvar(R_USER_CONFIG_DIR = withr::local_tempdir())
check_list = list()
check_list$setup = summarise_setup(server, token)
check_list$audit_details = audit_details(get_server(), get_token())
Expand Down
5 changes: 2 additions & 3 deletions R/deploy_flask-r6.R
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ check_deploy_python_flask = R6::R6Class(
#' @description Checks deployment of Quarto document with HTML output
#' @param debug_level See check() for details
check = function(debug_level) {
if (isFALSE(.connect$rsconnect_python)) {
cli::cli_alert_info("rsconnect-python missing. Skipping Python test.")
invisible(NULL)
if (skip_without_rsconnect_python(self)) {
return(invisible(NULL))
}
python_dir = system.file(
"extdata",
Expand Down
19 changes: 14 additions & 5 deletions R/deploy_python.R
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,13 @@ deploy_python = function(
rsconnect_type,
# Quarto and Voila deploy a document; the apps deploy the directory
if (grepl("\\.(qmd|ipynb)$", python_files[1])) python_files[1] else ".",
"--server",
get_server(),
"--api-key",
get_token(),
"--new",
"--title",
title
),
wd = tmp_dir
wd = tmp_dir,
# Not as arguments, which other users on the machine can read with ps
env = c("current", CONNECT_SERVER = get_server(), CONNECT_API_KEY = get_token())
)
)
invisible(has_deployed$status == 0)
Expand Down Expand Up @@ -83,3 +81,14 @@ cleanup_python = function(tmp_dir, debug_level, title) {

invisible(NULL)
}

# Log the check as skipped, so the report lists it, when rsconnect-python is missing
skip_without_rsconnect_python = function(check) {
if (!isFALSE(.connect$rsconnect_python)) {
return(FALSE)
}
cli::cli_alert_info("rsconnect-python missing. Skipping Python test.")
check$start_logger()
check$stop_logger(NA)
TRUE
}
4 changes: 2 additions & 2 deletions R/deploy_quarto-r6.R
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ check_deploy_quarto_python = R6::R6Class(
#' @param account Connect username
#' @param debug_level See check() for details
check = function(debug_level, account = NULL) {
if (is.null(account)) {
account = get_username()
if (skip_without_rsconnect_python(self)) {
return(invisible(NULL))
}
python_dir = system.file(
"extdata",
Expand Down
3 changes: 1 addition & 2 deletions R/deploy_streamlit-r6.R
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ check_deploy_python_streamlit = R6::R6Class(
#' @description Checks deployment of Python Streamlit app
#' @param debug_level See check() for details
check = function(debug_level) {
if (isFALSE(.connect$rsconnect_python)) {
cli::cli_alert_info("rsconnect-python missing. Skipping Python test.")
if (skip_without_rsconnect_python(self)) {
return(invisible(NULL))
}
python_dir = system.file(
Expand Down
3 changes: 1 addition & 2 deletions R/deploy_voila-r6.R
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ check_deploy_python_voila = R6::R6Class(
#' @description Checks deployment of a Voila notebook
#' @param debug_level See check() for details
check = function(debug_level) {
if (isFALSE(.connect$rsconnect_python)) {
cli::cli_alert_info("rsconnect-python missing. Skipping Python test.")
if (skip_without_rsconnect_python(self)) {
return(invisible(NULL))
}
python_dir = system.file(
Expand Down
13 changes: 2 additions & 11 deletions R/quarto-helpers.R
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,6 @@ get_quarto_user_roles = function(out) {
#' @rdname get_quarto_old_users
#' @export
get_quarto_locked_user_apps = function(out) {
apps = out$users_details$apps
if (is.na(apps)) {
return(0L)
}

app_creators = purrr::map_df(
apps,
~ dplyr::tibble(owner = .x[["owner_username"]], locked = .x$owner_locked)
)
locked_users = dplyr::filter(app_creators, .data$locked) # nolint: object_usage_linter
nrow(locked_users)
# NULL on an evaluation licence, which skips the user audit
sum(out$users_details$locked_apps$n)
}
8 changes: 4 additions & 4 deletions R/summarise_users.R
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ summarise_users = function(server, token, debug_level) {
# XXX: I don't think this is right
# Evaluation copies should have apps???
if (is_evaluation(settings)) {
apps = NA
locked_apps = NULL
} else {
print_audit_users(user_list)
apps = print_audit_user_apps(client, debug_level)
locked_apps = print_audit_user_apps(client, debug_level)
}
list(user_list = user_list, apps = apps)
list(user_list = user_list, locked_apps = locked_apps)
}

is_evaluation = function(settings) {
Expand Down Expand Up @@ -80,5 +80,5 @@ print_audit_user_apps = function(client, debug_level) {
"{row$n} locked application{?s} owned by {cli::style_italic(row$username)}"
)
}
content
locked_content
}
25 changes: 23 additions & 2 deletions tests/testthat/test-deploy-python.R
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ test_that("A failed Python deploy still deletes the content Connect created", {
expect_identical(calls$deleted, "a")
})

test_that("Python deploys let Connect choose the Python version", {
test_that("Python deploys let Connect choose the Python and keep the key off the command line", {
calls = new.env()
testthat::local_mocked_bindings(
get_server = function(...) "https://connect.example.com/",
Expand All @@ -33,8 +33,9 @@ test_that("Python deploys let Connect choose the Python version", {
cleanup_python = function(...) invisible(NULL)
)
testthat::local_mocked_bindings(
run = function(command, args, wd, ...) {
run = function(command, args, wd, env, ...) {
calls$args = args
calls$env = env
calls$pyproject = readLines(file.path(wd, "pyproject.toml"))
list(status = 0L)
},
Expand All @@ -45,4 +46,24 @@ test_that("Python deploys let Connect choose the Python version", {
expect_true(deploy_python(python_dir, "hash.ipynb", "voila", debug_level = 0))
expect_true('requires-python = ">=3.10"' %in% calls$pyproject)
expect_identical(calls$args[1:3], c("deploy", "voila", "hash.ipynb"))
expect_false("key" %in% calls$args)
expect_identical(calls$env[["CONNECT_API_KEY"]], "key")
})

test_that("Python checks are logged as skipped without rsconnect-python", {
old = .connect$rsconnect_python
withr::defer(assign("rsconnect_python", old, envir = .connect))
.connect$rsconnect_python = FALSE
testthat::local_mocked_bindings(deploy_python = function(...) stop("should not deploy"))
checks = list(
check_deploy_python_flask,
check_deploy_python_streamlit,
check_deploy_python_voila,
check_deploy_quarto_python
)
for (check in checks) {
obj = check$new(dir = withr::local_tempdir())
obj$check(debug_level = 0)
expect_true(is.na(obj$get_log()$passed))
}
})
9 changes: 9 additions & 0 deletions tests/testthat/test-quarto-helpers.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
test_that("get_quarto_locked_user_apps() counts content owned by locked users", {
locked_apps = tibble::tibble(username = c("ann", "bob"), n = c(2L, 1L))
expect_identical(
get_quarto_locked_user_apps(list(users_details = list(locked_apps = locked_apps))),
3L
)
# An evaluation licence skips the user audit
expect_identical(get_quarto_locked_user_apps(list(users_details = list(locked_apps = NULL))), 0L)
})
Loading