diff --git a/DESCRIPTION b/DESCRIPTION index 2f0b2351..e0bfe79b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -26,6 +26,7 @@ Depends: Imports: dplyr, emoji, + git2r, httr, jsonlite, lifecycle, diff --git a/NAMESPACE b/NAMESPACE index df052d72..8adec1a8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,7 +1,13 @@ # Generated by roxygen2: do not edit by hand +export(check_git_sslverify) +export(check_gitconfig_location) +export(check_github_pat) +export(check_proxy_settings) +export(check_renv_download_method) export(comma_sep) export(create_project) +export(diagnostic_test) export(fetch_countries) export(fetch_lads) export(fetch_las) diff --git a/R/diagnostic_test.R b/R/diagnostic_test.R new file mode 100644 index 00000000..0de92cb1 --- /dev/null +++ b/R/diagnostic_test.R @@ -0,0 +1,324 @@ +#' Diagnostic testing +#' +#' @description +#' Run a set of diagnostic tests to check for common issues found when setting +#' up R on a DfE +#' system. This includes: +#' - Checking for proxy settings in the Git configuration +#' - Checking for correct download method used by renv (curl) +#' +#' @inheritParams check_proxy_settings +#' +#' @return List object of detected parameters +#' @export +#' +#' @examples +#' diagnostic_test() +diagnostic_test <- function( + clean = FALSE, + verbose = FALSE) { + results <- c( + check_proxy_settings(clean = clean, verbose = verbose), + check_git_sslverify(clean = clean, verbose = verbose), + check_github_pat(clean = clean, verbose = verbose), + check_renv_download_method(clean = clean, verbose = verbose) + ) + return(results) +} + +#' Title +#' +#' @param clean +#' +#' @returns MULL +#' @export +#' +#' @examples +#' check_gitconfig_location() +check_gitconfig_location <- function( + clean = FALSE) { + config_files <- git2r:::git_config_files() + global_path <- config_files |> + dplyr::filter(file == "global") |> + dplyr::pull("path") + if (grepl("Users", global_path)) { + message( + "FAIL: Your global .gitconfig file is saved in a non-standard location." + ) + message("It may not be recognised and read in when using Git.") + message("We recommend using the standard location:") + message(" C:/Users//.gitconfig") + message("It contains the following settings:") + print(git2r::config() |> magrittr::extract2("global")) + } +} + + +#' Check proxy settings +#' +#' @description +#' This script checks for "bad" proxy settings. Prior to the pandemic, analysts +#' in the DfE would need to add some proxy settings to either their GitHub +#' config or environment variables. +#' These settings now prevent Git from connecting to remote archives on GitHub +#' and Azure DevOps, so this script identifies and (if clean=TRUE is set) +#' removes them. +#' +#' @param proxy_setting_names Vector of proxy parameters to check for. Default: +#' c("http.proxy", "https.proxy") +#' @param clean Attempt to clean settings +#' @param verbose Run in verbose mode +#' +#' @return List of problem proxy settings +#' @export +#' +#' @examples +#' check_proxy_settings() +check_proxy_settings <- function( + proxy_setting_names = c("http.proxy", "https.proxy"), + clean = FALSE, + verbose = FALSE) { + proxy_settings_detected <- FALSE + # Check for proxy settings in the Git configuration + git_config <- git2r::config() + proxy_config <- git_config |> + magrittr::extract2("global") |> + magrittr::extract(proxy_setting_names) + proxy_config <- proxy_config[!is.na(names(proxy_config))] + if (any(!is.na(names(proxy_config)))) { + dfeR::toggle_message( + "Found proxy settings in Git config:", + verbose = verbose + ) + paste(names(proxy_config), "=", proxy_config, collapse = "\n") |> + toggle_message(verbose = verbose) + proxy_settings_detected <- TRUE + if (clean) { + proxy_args <- proxy_config |> + lapply(function(list) { + NULL + }) + rlang::inject(git2r::config(!!!proxy_args, global = TRUE)) + message("FIXED: Git proxy settings have been cleared.") + } else { + message("FAIL: Git proxy setting have been left in place.") + } + } else { + proxy_config <- NULL + } + # Check for proxy settings set in the system environment (dfeR version) + # The sys variables have a slightly different syntax with "_" rather than "." + proxy_sysvar_names <- gsub("\\.", "_", proxy_setting_names) + proxy_system <- Sys.getenv(proxy_sysvar_names) |> + as.list() + proxy_system <- proxy_system[proxy_system != ""] + if (any(proxy_system != "")) { + dfeR::toggle_message( + "Found proxy settings in system environment:", + verbose = verbose + ) + paste(names(proxy_system), "=", proxy_system, collapse = "\n") |> + toggle_message(verbose = verbose) + proxy_settings_detected <- TRUE + if (clean) { + proxy_args <- proxy_system |> + lapply(function(list) { + "" + }) + rlang::inject(Sys.setenv(!!!proxy_args)) + message("FIXED: System environment proxy settings have been cleared.") + } else { + message( + "FAIL: System environment proxy settings have been left in place." + ) + } + } else { + proxy_system <- NULL + } + if (!proxy_settings_detected) { + message( + "PASS: No proxy settings found in your Git config or system environment." + ) + } + return(list(git = proxy_config, system = proxy_system)) +} + +#' Check Git sslverify setting +#' +#' @description +#' This script checks the values of the specified settings in the Git config and +#' if they're set to FALSE, it changes them to "TRUE" if clean=TRUE. +#' The defaut is to check for "http.sslVerify" and "https.sslVerify" +#' +#' @param ssl_verify_vars Vector of variables to check for in the Git config +#' @param clean Attempt to clean settings +#' @param verbose Run in verbose mode +#' +#' @return List of sslverify settings +#' @export +#' +#' @examples +#' check_git_sslverify() +check_git_sslverify <- function( + ssl_verify_vars = c("http.sslverify", "https.sslverify"), + clean = FALSE, + verbose = FALSE) { + # Check for proxy settings in the Git configuration + git_config <- git2r::config() |> + magrittr::extract2("global") |> + magrittr::extract(ssl_verify_vars) + git_config <- git_config[!is.na(names(git_config))] + if (any(!is.na(names(git_config)))) { + dfeR::toggle_message( + "Found specified settings in Git config:", + verbose = verbose + ) + paste(names(git_config), "=", git_config, collapse = "\n") |> + toggle_message(verbose = verbose) + if (any(tolower(git_config) == "false")) { + if (clean) { + git_args <- git_config[tolower(git_config) == "false"] |> + lapply(function(list) { + "TRUE" + }) + rlang::inject(git2r::config(!!!git_args, global = TRUE)) + message("FIXED: Specified Git settings have been set") + } else { + message("FAIL: sslverify is set to FALSE.") + message("Specified Git settings have been left in place.") + } + } else { + message( + "PASS: sslverify is set to TRUE." + ) + } + } else { + git_config <- NULL + message( + "PASS: sslverify is not explicitly set." + ) + } + return(list(ssl_verify = git_config)) +} + + +#' Check GITHUB_PAT setting +#' +#' @description +#' If the GITHUB_PAT keyword is set, then it can cause issues with R installing +#' packages from GitHub (usually with an error of "ERROR \[curl: (22) The +#' requested URL returned error: 401\]"). This script checks whether the keyword +#' is set and can then clear it (if clear=TRUE). +#' The user will then need to identify where the "GITHUB_PAT" variable is being +#' set from and remove it to permanently fix the issue. +#' +#' @inheritParams check_proxy_settings +#' +#' @return List object containing the github_pat keyword content. +#' @export +#' +#' @examples +#' check_github_pat() +check_github_pat <- function( + clean = FALSE, + verbose = FALSE) { + github_pat <- Sys.getenv("GITHUB_PAT") + # Replace above to remove non alphanumeric characters when run on GitHub + # Actions + if (github_pat != "") { + message( + "FAIL: GITHUB_PAT is set to ", + github_pat, + ". This may cause issues with installing packages from GitHub", + " such as dfeR and dfeshiny." + ) + if (clean) { + message("Clearing GITHUB_PAT keyword from system settings.") + Sys.unsetenv("GITHUB_PAT") + message( + "This issue may recur if you have some software that is", + "initialising the GITHUB_PAT keyword automatically." + ) + } + } else { + message("PASS: The GITHUB_PAT system variable is clear.") + } + return(list(GITHUB_PAT = github_pat)) +} + +#' Check renv download method +#' +#' @description +#' The renv package can retrieve packages either using curl or wininet, but +#' wininet doesn't work from within the DfE network. This script checks for +#' the parameter controlling which of these is used (RENV_DOWNLOAD_METHOD) and +#' sets it to use curl. +#' +#' @param renviron_file Location of .Renviron file. Default: ~/.Renviron +#' @inheritParams check_proxy_settings +#' +#' @return List object containing RENV_DOWNLOAD_METHOD +#' @export +#' +#' @examples +#' check_renv_download_method() +check_renv_download_method <- function( + renviron_file = "~/.Renviron", + clean = FALSE, + verbose = FALSE) { + if (file.exists(renviron_file)) { + .renviron <- readLines(renviron_file) + } else { + .renviron <- c() + } + rdm_present <- .renviron %>% stringr::str_detect("RENV_DOWNLOAD_METHOD") + if (any(rdm_present)) { + current_setting_message <- paste0( + "RENV_DOWNLOAD_METHOD is currently set to:\n ", + .renviron[rdm_present] + ) + detected_method <- .renviron[rdm_present] |> + stringr::str_split("=") |> + unlist() |> + magrittr::extract(2) + } else { + current_setting_message <- "RENV_DOWNLOAD_METHOD is not currently set." + detected_method <- NA + } + if (is.na(detected_method) || detected_method != "\"curl\"") { + if (clean) { + if (any(rdm_present)) { + .renviron <- .renviron[!rdm_present] + } + .renviron <- c( + .renviron, + "RENV_DOWNLOAD_METHOD=\"curl\"" + ) + cat(.renviron, file = renviron_file, sep = "\n") + message( + paste0( + "FIXED: The renv download method has been set to curl in your ", + ".Renviron file." + ) + ) + readRenviron(renviron_file) + } else { + dfeR::toggle_message(paste("FAIL:", current_setting_message), + verbose = verbose + ) + message("If you wish to manually update your .Renviron file:") + message(" - Run the command in the R console to open .Renviron:") + message(" usethis::edit_r_environ()") + if (any(rdm_present)) { + message(" - Remove the following line from .Renviron:") + message(" ", .renviron[rdm_present]) + } + message(" - Add the following line to .Renviron:") + message(" RENV_DOWNLOAD_METHOD=\"curl\"") + message("Or run `dfeR::check_renv_download_method(clean=TRUE)`") + } + } else { + message("PASS: Your RENV_DOWNLOAD_METHOD is set to curl.") + } + return(list(RENV_DOWNLOAD_METHOD = detected_method)) +} diff --git a/_pkgdown.yml b/_pkgdown.yml index 7810c6d4..1e5dd7e6 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -43,6 +43,14 @@ reference: contents: - starts_with("format_") +- title: System diagnostics + desc: Functions to diagnose and fix common system issues + contents: + - diagnostic_test + - check_proxy_settings + - check_github_pat + - check_renv_download_method + - title: Helpers desc: > Functions used in other functions across the package, exported as they can also diff --git a/man/check_git_sslverify.Rd b/man/check_git_sslverify.Rd new file mode 100644 index 00000000..9340e6ae --- /dev/null +++ b/man/check_git_sslverify.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/diagnostic_test.R +\name{check_git_sslverify} +\alias{check_git_sslverify} +\title{Check Git sslverify setting} +\usage{ +check_git_sslverify( + ssl_verify_vars = c("http.sslverify", "https.sslverify"), + clean = FALSE, + verbose = FALSE +) +} +\arguments{ +\item{ssl_verify_vars}{Vector of variables to check for in the Git config} + +\item{clean}{Attempt to clean settings} + +\item{verbose}{Run in verbose mode} +} +\value{ +List of sslverify settings +} +\description{ +This script checks the values of the specified settings in the Git config and +if they're set to FALSE, it changes them to "TRUE" if clean=TRUE. +The defaut is to check for "http.sslVerify" and "https.sslVerify" +} +\examples{ +check_git_sslverify() +} diff --git a/man/check_gitconfig_location.Rd b/man/check_gitconfig_location.Rd new file mode 100644 index 00000000..a7b5c36e --- /dev/null +++ b/man/check_gitconfig_location.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/diagnostic_test.R +\name{check_gitconfig_location} +\alias{check_gitconfig_location} +\title{Title} +\usage{ +check_gitconfig_location(clean = FALSE) +} +\arguments{ +\item{clean}{} +} +\description{ +Title +} diff --git a/man/check_github_pat.Rd b/man/check_github_pat.Rd new file mode 100644 index 00000000..6390f1bf --- /dev/null +++ b/man/check_github_pat.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/diagnostic_test.R +\name{check_github_pat} +\alias{check_github_pat} +\title{Check GITHUB_PAT setting} +\usage{ +check_github_pat(clean = FALSE, verbose = FALSE) +} +\arguments{ +\item{clean}{Attempt to clean settings} + +\item{verbose}{Run in verbose mode} +} +\value{ +List object containing the github_pat keyword content. +} +\description{ +If the GITHUB_PAT keyword is set, then it can cause issues with R installing +packages from GitHub (usually with an error of "ERROR [curl: (22) The +requested URL returned error: 401]"). This script checks whether the keyword +is set and can then clear it (if clear=TRUE). +The user will then need to identify where the "GITHUB_PAT" variable is being +set from and remove it to permanently fix the issue. +} +\examples{ +check_github_pat() +} diff --git a/man/check_proxy_settings.Rd b/man/check_proxy_settings.Rd new file mode 100644 index 00000000..4fbaa4c3 --- /dev/null +++ b/man/check_proxy_settings.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/diagnostic_test.R +\name{check_proxy_settings} +\alias{check_proxy_settings} +\title{Check proxy settings} +\usage{ +check_proxy_settings( + proxy_setting_names = c("http.proxy", "https.proxy"), + clean = FALSE, + verbose = FALSE +) +} +\arguments{ +\item{proxy_setting_names}{Vector of proxy parameters to check for. Default: +c("http.proxy", "https.proxy")} + +\item{clean}{Attempt to clean settings} + +\item{verbose}{Run in verbose mode} +} +\value{ +List of problem proxy settings +} +\description{ +This script checks for "bad" proxy settings. Prior to the pandemic, analysts +in the DfE would need to add some proxy settings to either their GitHub +config or environment variables. +These settings now prevent Git from connecting to remote archives on GitHub +and Azure DevOps, so this script identifies and (if clean=TRUE is set) +removes them. +} +\examples{ +check_proxy_settings() +} diff --git a/man/check_renv_download_method.Rd b/man/check_renv_download_method.Rd new file mode 100644 index 00000000..5ec319f1 --- /dev/null +++ b/man/check_renv_download_method.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/diagnostic_test.R +\name{check_renv_download_method} +\alias{check_renv_download_method} +\title{Check renv download method} +\usage{ +check_renv_download_method( + renviron_file = "~/.Renviron", + clean = FALSE, + verbose = FALSE +) +} +\arguments{ +\item{renviron_file}{Location of .Renviron file. Default: ~/.Renviron} + +\item{clean}{Attempt to clean settings} + +\item{verbose}{Run in verbose mode} +} +\value{ +List object containing RENV_DOWNLOAD_METHOD +} +\description{ +The renv package can retrieve packages either using curl or wininet, but +wininet doesn't work from within the DfE network. This script checks for +the parameter controlling which of these is used (RENV_DOWNLOAD_METHOD) and +sets it to use curl. +} +\examples{ +check_renv_download_method() +} diff --git a/man/diagnostic_test.Rd b/man/diagnostic_test.Rd new file mode 100644 index 00000000..c75809e7 --- /dev/null +++ b/man/diagnostic_test.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/diagnostic_test.R +\name{diagnostic_test} +\alias{diagnostic_test} +\title{Diagnostic testing} +\usage{ +diagnostic_test(clean = FALSE, verbose = FALSE) +} +\arguments{ +\item{clean}{Attempt to clean settings} + +\item{verbose}{Run in verbose mode} +} +\value{ +List object of detected parameters +} +\description{ +Run a set of diagnostic tests to check for common issues found when setting +up R on a DfE +system. This includes: +\itemize{ +\item Checking for proxy settings in the Git configuration +\item Checking for correct download method used by renv (curl) +} +} +\examples{ +diagnostic_test() +} diff --git a/tests/testthat/test-diagnostic_test.R b/tests/testthat/test-diagnostic_test.R new file mode 100644 index 00000000..5c7ed2e1 --- /dev/null +++ b/tests/testthat/test-diagnostic_test.R @@ -0,0 +1,90 @@ +test_that("Check proxy settings identifies and removes proxy setting", { + # Set a dummy config parameter for the purposes of testing + proxy_setting_names <- c("http.proxy.test", "https.proxy.test") + # Check functionality with Git config + git2r::config(http.proxy.test = "this-is-a-test-entry", global = TRUE) + # Check that check_proxy_settings identifies the rogue entry + expect_equal( + check_proxy_settings( + proxy_setting_names = proxy_setting_names, + clean = FALSE + ) |> + suppressMessages(), + list( + git = list(http.proxy.test = "this-is-a-test-entry"), + system = NULL + ) + ) + # Run the check in clean mode + expect_equal( + check_proxy_settings( + proxy_setting_names = proxy_setting_names, + clean = TRUE + ) |> + suppressMessages(), + list( + git = list(http.proxy.test = "this-is-a-test-entry"), + system = NULL + ) + ) + # And now run again to see if clean mode worked in removing the rogue setting + expect_equal( + check_proxy_settings( + proxy_setting_names = proxy_setting_names + ) |> + suppressMessages(), + list( + git = NULL, + system = NULL + ) + ) + # Check functionality with system environment variables + Sys.setenv(http_proxy_test = "this-is-a-test-entry") + # Check that check_proxy_settings identifies the rogue entry + expect_equal( + check_proxy_settings( + proxy_setting_names = proxy_setting_names, + clean = FALSE + ) |> + suppressMessages(), + list( + git = NULL, + system = list(http_proxy_test = "this-is-a-test-entry") + ) + ) + # Run the check in clean mode + expect_equal( + check_proxy_settings( + proxy_setting_names = proxy_setting_names, + clean = TRUE + ) |> + suppressMessages(), + list( + git = NULL, + system = list(http_proxy_test = "this-is-a-test-entry") + ) + ) + # And now run again to see if clean mode worked in removing the rogue setting + expect_equal( + check_proxy_settings( + proxy_setting_names = proxy_setting_names + ) |> + suppressMessages(), + list( + git = NULL, + system = NULL + ) + ) +}) + +test_that("Check RENV_DOWNLOAD_METHOD", { + # Check that check_proxy_settings identifies the rogue entry + expect_equal( + check_renv_download_method( + ".Renviron_test", + clean = FALSE + ) |> + suppressMessages(), + list(RENV_DOWNLOAD_METHOD = NA) + ) +})