diff --git a/DESCRIPTION b/DESCRIPTION index 1d8e94d..c438f49 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: audit.base Title: Base package for Posit Checks -Version: 0.7.5 +Version: 0.7.6 Authors@R: person("Jumping", "Rivers", , "info@jumpingrivers.com", role = c("aut", "cre")) Description: Base package for sharing classes between posit audit diff --git a/NEWS.md b/NEWS.md index e288637..d341d67 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +# audit.base 0.7.6 _2026-10-16_ + * fix: `check_sys_deps()` reads the distribution from os-release `ID` and `ID_LIKE`, and passes `VERSION_ID` to Posit Package Manager, so Ubuntu 26.04, RHEL 10, Debian, Rocky Linux and AlmaLinux work. If P3M or the package listing doesn't support a system, it warns with the reason and returns `NA` rather than stopping the audit (#58) + * fix: System requirements come from `packagemanager.posit.co`, Posit Package Manager's current address + # audit.base 0.7.5 _2026-10-16_ * fix: The Quarto version list comes from GitHub's releases, so a new minor release no longer marks the previous one as past EOL (#60) diff --git a/R/check_sys_deps.R b/R/check_sys_deps.R index 68dcfbf..174920f 100644 --- a/R/check_sys_deps.R +++ b/R/check_sys_deps.R @@ -14,6 +14,10 @@ check_sys_deps = function(os_release, installed_libs, debug_level = 0:2) { cli::cli_h2("Systems Libraries") # Clean up response pkg_look_up = get_os_sys_deps(os_release) + # get_os_sys_deps() and clean_libs() have warned why + if (!is.data.frame(pkg_look_up) || anyNA(installed_libs)) { + return(NA) + } # Determine which libs are missing missing_libs = pkg_look_up[!(pkg_look_up$sys_libs %in% installed_libs), ] @@ -31,21 +35,18 @@ check_sys_deps = function(os_release, installed_libs, debug_level = 0:2) { # Contents of /etc/os-release get_os_sys_deps = function(os_release) { os_release_df = os_release_to_df(os_release) - version_id = os_release_df[os_release_df$name == "version_id", ]$value - if (is_ubuntu(os_release_df)) { - id = "ubuntu" - } else if (is_redhat(os_release_df)) { - id = "redhat" - version_id = stringr::str_remove(version_id, "\\..*") - } else if (is_centos(os_release_df)) { - id = "centos" - version_id = stringr::str_remove(version_id, "\\..*") - } else { - cli::cli_abort("This OS isn't supported") + system = get_p3m_system(os_release_df) + if (is.na(system$distribution)) { + os_name = os_release_value(os_release_df, "pretty_name") # nolint + cli::cli_warn("Can't check system libraries: Posit Package Manager doesn't support {os_name}") + return(NA) + } + reqs = get_pkg_requirements(distribution = system$distribution, release = system$release) + if (!is.data.frame(reqs)) { + return(NA) } - reqs = get_pkg_requirements(distribution = id, release = version_id) reqs = reqs[!is.na(reqs$sys_libs), ] - if (id == "ubuntu") { + if (system$distribution %in% c("ubuntu", "debian")) { reqs$sys_libs = switch_ubuntu_aliases(reqs$sys_libs) } reqs @@ -59,14 +60,15 @@ get_os_sys_deps = function(os_release) { get_installed_libs = function() { os_release = get_os_release() os_release_df = os_release_to_df(os_release) - if (is_ubuntu(os_release_df)) { + tool = get_package_tool(os_release_df) + if (identical(tool, "apt")) { libs = processx::run("apt", args = c("list", "--installed")) libs = stringr::str_split(libs$stdout, "\n")[[1]][-1] - } else if (is_redhat(os_release_df) || is_centos(os_release_df)) { + } else if (identical(tool, "yum")) { libs = processx::run("yum", args = c("list", "installed"))$stdout libs = stringr::str_split(libs, "\n")[[1]][-(1:2)] } else { - cli::cli_abort("We don't support this OS yet") + libs = character(0) } clean_libs(os_release, libs) } @@ -77,13 +79,16 @@ get_installed_libs = function() { #' @rdname get_installed_libs clean_libs = function(os_release, libs) { os_release_df = os_release_to_df(os_release) - if (is_ubuntu(os_release_df)) { + tool = get_package_tool(os_release_df) + if (identical(tool, "apt")) { libs = stringr::str_match(libs, "^[^/]*")[, 1] libs = switch_ubuntu_aliases(libs) - } else if (is_redhat(os_release_df) || is_centos(os_release_df)) { + } else if (identical(tool, "yum")) { libs = stringr::str_match(libs, "^[^\\.]*")[, 1] } else { - cli::cli_abort("We don't support this OS yet") + os_name = os_release_value(os_release_df, "pretty_name") # nolint + cli::cli_warn("Can't list the installed system libraries on {os_name}") + return(NA_character_) } sort(libs) } diff --git a/R/get_pkg_requirements.R b/R/get_pkg_requirements.R index 3e32745..5859b2d 100644 --- a/R/get_pkg_requirements.R +++ b/R/get_pkg_requirements.R @@ -1,12 +1,11 @@ -# https://packagemanager.rstudio.com/__api__/swagger/index.html#/default/get_repos +# https://packagemanager.posit.co/__api__/swagger/index.html#/default/get_repos +# NA, with P3M's reason as a warning, when P3M doesn't support the system get_pkg_requirements = function( - distribution = c("ubuntu", "redhat", "centos"), - release = c("18.04", "20.04", "22.04", "24.04", "7", "8", "9"), - base_url = "https://packagemanager.rstudio.com/__api__/repos/", + distribution, + release, + base_url = "https://packagemanager.posit.co/__api__/repos/", repo_id = 2 ) { - distribution = match.arg(distribution) - release = match.arg(release) config_url = glue::glue( "{repo_id}/sysreqs?all=true&distribution={distribution}&release={release}" ) # nolint @@ -15,8 +14,15 @@ get_pkg_requirements = function( "Connecting to {url} for system requirements" ) - res = httr::GET(url) + res = httr::GET(url, httr::timeout(60)) r = httr::content(res) + if (httr::http_error(res)) { + reason = if (is.list(r) && is.character(r$error)) r$error else httr::http_status(res)$message # nolint + cli::cli_warn( + "Posit Package Manager can't list system requirements for {distribution} {release}: {reason}" + ) + return(NA) + } purrr::map_df( r$requirements, diff --git a/R/os-helper-functions.R b/R/os-helper-functions.R index 71ef569..6c65720 100644 --- a/R/os-helper-functions.R +++ b/R/os-helper-functions.R @@ -1,16 +1,42 @@ -is_ubuntu = function(os_release_df) { - os_name = os_release_df[os_release_df$name == "name", ]$value - stringr::str_detect(os_name, pattern = "ubuntu") +# Posit Package Manager's distribution and release, from os-release ID, then ID_LIKE. +# Rocky Linux and AlmaLinux list rhel in ID_LIKE, so they use the Red Hat requirements. +get_p3m_system = function(os_release_df) { + p3m_names = c( + ubuntu = "ubuntu", + debian = "debian", + centos = "centos", + rhel = "redhat", + "opensuse-leap" = "opensuse", + sles = "sle" + ) + ids = c( + os_release_value(os_release_df, "id"), + stringr::str_split(os_release_value(os_release_df, "id_like"), " ")[[1]] + ) + distribution = unname(p3m_names[ids[ids %in% names(p3m_names)]])[1L] + release = os_release_value(os_release_df, "version_id") + # P3M uses the major version for these: 9, not 9.4 + if (distribution %in% c("debian", "redhat", "centos")) { + release = stringr::str_remove(release, "\\..*") + } + list(distribution = distribution, release = release) } -is_redhat = function(os_release_df) { - os_name = os_release_df[os_release_df$name == "name", ]$value - stringr::str_detect(os_name, pattern = "red hat") +# apt or yum, which list installed packages in different formats; NA for any other OS +get_package_tool = function(os_release_df) { + distribution = get_p3m_system(os_release_df)$distribution + if (distribution %in% c("ubuntu", "debian")) { + "apt" + } else if (distribution %in% c("redhat", "centos")) { + "yum" + } else { + NA_character_ + } } -is_centos = function(os_release_df) { - os_name = os_release_df[os_release_df$name == "name", ]$value - stringr::str_detect(os_name, pattern = "centos") +os_release_value = function(os_release_df, name) { + value = os_release_df$value[os_release_df$name == name] + if (length(value) == 0L) "" else value[1L] } get_os_release = function() { diff --git a/R/quarto-helpers.R b/R/quarto-helpers.R index 2cdb625..b0ca65e 100644 --- a/R/quarto-helpers.R +++ b/R/quarto-helpers.R @@ -28,6 +28,10 @@ get_quarto_server_header = function(out) { #' @export get_quarto_sys_deps = function(out) { sys_deps = out$sys_deps + # NA when the system libraries couldn't be checked + if (!is.data.frame(sys_deps)) { + sys_deps = tibble::tibble(pkg = character(0), sys_libs = character(0)) + } sys_deps %>% dplyr::group_by(.data$sys_libs) %>% dplyr::reframe( diff --git a/tests/testthat/test-os-helper-functions.R b/tests/testthat/test-os-helper-functions.R index 0bba955..7d6f47e 100644 --- a/tests/testthat/test-os-helper-functions.R +++ b/tests/testthat/test-os-helper-functions.R @@ -6,3 +6,47 @@ test_that("OS tests", { libs = get_installed_libs() expect_gt(length(libs), 1000) }) + +test_that("get_p3m_system() maps os-release to P3M's distribution and release", { + os_releases = list( + ubuntu = c("ID=ubuntu", "ID_LIKE=debian", "VERSION_ID=\"26.04\""), + debian = c("ID=debian", "VERSION_ID=\"12\""), + rhel = c("ID=\"rhel\"", "ID_LIKE=\"fedora\"", "VERSION_ID=\"10.0\""), + centos = c("ID=\"centos\"", "ID_LIKE=\"rhel fedora\"", "VERSION_ID=\"7\""), + rocky = c("ID=\"rocky\"", "ID_LIKE=\"rhel centos fedora\"", "VERSION_ID=\"9.4\""), + alma = c("ID=\"almalinux\"", "ID_LIKE=\"rhel centos fedora\"", "VERSION_ID=\"8.10\""), + suse = c("ID=\"opensuse-leap\"", "ID_LIKE=\"suse opensuse\"", "VERSION_ID=\"15.6\""), + arch = c("ID=arch", "BUILD_ID=rolling") + ) + expected = list( + ubuntu = c("ubuntu", "26.04"), + debian = c("debian", "12"), + rhel = c("redhat", "10"), + centos = c("centos", "7"), + rocky = c("redhat", "9"), + alma = c("redhat", "8"), + suse = c("opensuse", "15.6"), + arch = c(NA, "") + ) + for (os in names(os_releases)) { + system = get_p3m_system(os_release_to_df(os_releases[[os]])) + expect_identical(c(system$distribution, system$release), expected[[os]], label = os) + } +}) + +test_that("Unsupported systems warn and give NA instead of stopping", { + arch = c("PRETTY_NAME=\"Arch Linux\"", "ID=arch") + expect_warning(check_sys_deps(arch, "zlib"), "arch linux") + expect_identical(suppressWarnings(check_sys_deps(arch, "zlib")), NA) + expect_warning(clean_libs(arch, "zlib"), "arch linux") + expect_identical(suppressWarnings(clean_libs(arch, "zlib")), NA_character_) + + out = get_quarto_sys_deps(list(sys_deps = NA)) + expect_equal(colnames(out), c("sys_libs", "pkg", "n")) +}) + +test_that("P3M's reason for rejecting a system reaches the user", { + skip_if_offline("packagemanager.posit.co") + expect_warning(get_pkg_requirements("rockylinux", "8"), "Unsupported system") + expect_identical(suppressWarnings(get_pkg_requirements("rockylinux", "8")), NA) +})