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
4 changes: 2 additions & 2 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.10
Version: 0.8.11
Authors@R:
person("Jumping", "Rivers", , "info@jumpingrivers.com", role = c("aut", "cre"))
Description: Posit Connect Health Check. Deploys various content types to
Expand All @@ -10,7 +10,7 @@ License: file LICENSE
Depends:
R (>= 4.1.0)
Imports:
audit.base (>= 0.7.2),
audit.base (>= 0.7.6),
cli,
connectapi (>= 0.12.1),
dplyr,
Expand Down
7 changes: 7 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# audit.connect 0.8.11

## Bug fixes and minor improvements

* `check()` carries on when the system-library check can't run. An error from the endpoint, an unexpected reply or an unsupported OS gives a warning and `sys_deps = NA`, and the deployment checks still run (#63).
* The sys-deps endpoint lists installed packages on Debian, Rocky Linux and AlmaLinux, as well as Ubuntu and RHEL. Requires audit.base 0.7.6 (#63).

# audit.connect 0.8.10

## Bug fixes and minor improvements
Expand Down
34 changes: 27 additions & 7 deletions R/check_sys_deps.R
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,33 @@ check_sys_deps = function(debug_level = 0:2) {
# Ping endpoint
url = paste0(get_deploy_url(content), "installed")
auth_key = paste0("Key ", get_token())
resp = httr::GET(url, httr::add_headers(Authorization = auth_key))
rtn = jsonlite::parse_json(rawToChar(resp$content))
# Read by check_connect_timezone(), as the endpoint is the only code that runs on Connect
.connect$timezone = unlist(rtn$timezone)

clean_libs = audit.base::clean_libs(rtn$os_release, rtn$libs)
audit.base::check_sys_deps(rtn$os_release, clean_libs, debug_level)
resp = httr::GET(url, httr::add_headers(Authorization = auth_key), httr::timeout(120))
if (httr::http_error(resp)) {
cli::cli_warn(
"Can't check system libraries: the endpoint returned {httr::http_status(resp)$message}"
)
return(NA)
}
# An unsupported OS or an unexpected reply warns, so the deployment checks still run
tryCatch(
{
rtn = jsonlite::parse_json(rawToChar(resp$content))
# Read by check_connect_timezone(), as the endpoint is the only code that runs on Connect
.connect$timezone = unlist(rtn$timezone)
os_release = unlist(rtn$os_release)
libs = unlist(rtn$libs)
clean_libs = audit.base::clean_libs(os_release, libs)
# clean_libs() has warned that it can't read the package list
if (anyNA(clean_libs)) {
return(NA)
}
audit.base::check_sys_deps(os_release, clean_libs, debug_level)
},
error = function(e) {
cli::cli_warn(c("Can't check system libraries", "x" = conditionMessage(e)))
NA
}
)
}

setup_plumber_sys_deps_endpoint = function(app_dir, debug_level) {
Expand Down
2 changes: 1 addition & 1 deletion inst/extdata/check_sys_deps/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@
"checksum": "9fcd95b1ccc5ca6dba0ad125b62c3747"
},
"plumber.R": {
"checksum": "ed571a06f3580f5202a258fb5e8d6bb5"
"checksum": "cb29cfd98c2279f36771c28f0619831e"
},
"renv.lock": {
"checksum": "a564e763ab0a9b503272f0c962cef9c6"
Expand Down
24 changes: 21 additions & 3 deletions inst/extdata/check_sys_deps/plumber.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,35 @@
#
# Commented out. Don't need plumber to deploy
library("plumber")

# apt or yum, from os-release ID and ID_LIKE, so Debian, Rocky and AlmaLinux are listed too.
# audit.base decides whether Posit Package Manager supports the OS.
package_tool = function(os_release) {
ids = sub("^ID(_LIKE)?=", "", os_release[grepl("^ID(_LIKE)?=", os_release)])
ids = strsplit(tolower(gsub("\"", "", ids)), " ")
ids = unlist(ids)
if (any(ids %in% c("debian", "ubuntu"))) {
"apt"
} else if (any(ids %in% c("rhel", "centos", "fedora"))) {
"yum"
} else {
NA_character_
}
}

#* @apiTitle Installed Sys Deps
#* @apiDescription Returns the OS, installed libraries and timezone of the connect server
#* @get /installed
function() {
os_release = readLines("/etc/os-release")
if (any(grepl("[u|U]buntu", os_release))) {
tool = package_tool(os_release)
if (identical(tool, "apt")) {
libs = system2("apt", args = c("list", "--installed"), stdout = TRUE)
} else if (any(grepl("red hat|centos", os_release, ignore.case = TRUE))) {
} else if (identical(tool, "yum")) {
libs = system2("yum", args = c("list", "installed"), stdout = TRUE)
} else {
os_release = libs = NA
# Still send os_release, so audit.base can say which OS isn't supported
libs = NA
}

# Sys.timezone() warns when timedatectl fails, but can still find the timezone.
Expand Down
30 changes: 30 additions & 0 deletions tests/testthat/test-cleanup.R
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,33 @@ test_that("cleanup_quarto() deletes by title when the deploy left no record", {
expect_identical(calls$deleted, "a")
expect_false(dir.exists(tmp_dir))
})

test_that("A bad endpoint reply or unsupported OS warns instead of stopping check()", {
testthat::local_mocked_bindings(
get_server = function(...) "https://connect.example.com/",
get_token = function(...) "key",
get_deploy_url = function(...) "https://connect.example.com/content/1/",
cleanup_plumber = function(...) NULL,
setup_plumber_sys_deps_endpoint = function(...) structure(list(), class = "Content")
)
testthat::local_mocked_bindings(poll_task = function(x, ...) x, .package = "connectapi")
reply = function(status, body) {
structure(
list(status_code = status, content = charToRaw(body), headers = list()),
class = "response"
)
}
replies = list(
reply(500L, "Internal Server Error"),
reply(200L, "<html>not JSON</html>"),
reply(
200L,
'{"os_release":["PRETTY_NAME=\\"Arch Linux\\"","ID=arch"],"libs":[null],"timezone":[""]}'
)
)
for (resp in replies) {
testthat::local_mocked_bindings(GET = function(...) resp, .package = "httr")
expect_warning(sys_deps <- check_sys_deps(debug_level = 0), "system libraries") # nolint
expect_identical(sys_deps, NA)
}
})
33 changes: 29 additions & 4 deletions tests/testthat/test-timezone.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
# The endpoint and its helper, without library("plumber")
load_sys_deps_endpoint = function() {
plumber_file = system.file("extdata", "check_sys_deps", "plumber.R", package = "audit.connect")
exprs = parse(plumber_file)
env = new.env()
for (expr in exprs[-c(1L, length(exprs))]) {
eval(expr, env)
}
env$endpoint = eval(exprs[[length(exprs)]], env)
env
}

test_that("check_connect_timezone() reports set, unset and unknown", {
on.exit(assign("timezone", NULL, envir = .connect))
.connect$timezone = "Europe/London"
Expand All @@ -11,14 +23,27 @@ test_that("check_connect_timezone() reports set, unset and unknown", {
test_that("The sys deps endpoint returns a timezone that survives JSON", {
# Connect only runs on Linux, so the endpoint does too
skip_if_not(Sys.info()[["sysname"]] == "Linux")
plumber_file = system.file("extdata", "check_sys_deps", "plumber.R", package = "audit.connect")
exprs = parse(plumber_file)
endpoint = eval(exprs[[length(exprs)]])
rtn = endpoint()
env = load_sys_deps_endpoint()
rtn = env$endpoint()
expect_type(rtn$timezone, "character")
expect_length(rtn$timezone, 1)
for (auto_unbox in c(TRUE, FALSE)) {
json = jsonlite::toJSON(rtn["timezone"], auto_unbox = auto_unbox)
expect_identical(unlist(jsonlite::parse_json(json)$timezone), rtn$timezone)
}
})

test_that("The sys deps endpoint lists packages on Debian and RHEL rebuilds", {
env = load_sys_deps_endpoint()
os_releases = list(
apt = c("ID=ubuntu", "ID_LIKE=debian"),
apt = "ID=debian",
yum = c("ID=\"rocky\"", "ID_LIKE=\"rhel centos fedora\""),
yum = c("ID=\"almalinux\"", "ID_LIKE=\"rhel centos fedora\""),
yum = c("ID=\"rhel\"", "ID_LIKE=\"fedora\"")
)
for (i in seq_along(os_releases)) {
expect_identical(env$package_tool(os_releases[[i]]), names(os_releases)[i])
}
expect_identical(env$package_tool(c("ID=\"opensuse-leap\"", "ID_LIKE=\"suse\"")), NA_character_)
})
Loading