diff --git a/DESCRIPTION b/DESCRIPTION index b42ffab2..fe24b08b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: orderly2 Title: Orderly Next Generation -Version: 1.99.48 +Version: 1.99.49 Authors@R: c(person("Rich", "FitzJohn", role = c("aut", "cre"), email = "rich.fitzjohn@gmail.com"), person("Robert", "Ashton", role = "aut"), diff --git a/R/location.R b/R/location.R index 1bdabf15..9ca308ca 100644 --- a/R/location.R +++ b/R/location.R @@ -75,26 +75,27 @@ ##' @param args Arguments to the location driver. The arguments here ##' will vary depending on the type used, see Details. ##' +##' @param verify Logical, indicating if we should verify that the +##' location can be used before adding. +##' ##' @inheritParams orderly_metadata ##' ##' @return Nothing ##' @export -orderly_location_add <- function(name, type, args, root = NULL) { +orderly_location_add <- function(name, type, args, verify = TRUE, root = NULL) { root <- root_open(root, require_orderly = FALSE) - assert_scalar_character(name, call = environment()) + assert_scalar_character(name) + assert_scalar_logical(verify) if (name %in% location_reserved_name) { cli::cli_abort("Cannot add a location with reserved name '{name}'") } location_check_new_name(root, name, environment()) - match_value(type, setdiff(location_types, location_reserved_name), - call = environment()) + match_value(type, setdiff(location_types, location_reserved_name)) - loc <- new_location_entry(name, type, args, call = environment()) if (type == "path") { assert_scalar_character(args$path, name = "path") - root_open(args$path, require_orderly = FALSE) } else if (type == "http") { assert_scalar_character(args$url, name = "url") } else if (type == "packit") { @@ -105,12 +106,23 @@ orderly_location_add <- function(name, type, args, root = NULL) { if (!is.null(args$token) && !is.null(args$save_token)) { cli::cli_abort("Cannot specify both 'token' and 'save_token'") } + if (is.null(args$save_token)) { + args$save_token <- is.null(args$token) + } + } + loc <- new_location_entry(name, type, args, call = environment()) + + if (verify) { + cli_alert_info("Testing location") + location_driver_create(type, args)$verify() + cli_alert_success("Location configured successfully") } config <- root$config config$location <- rbind(config$location, loc) rownames(config$location) <- NULL config_update(config, root) + cli_alert_success("Added location '{name}' ({type})") invisible() } @@ -122,9 +134,9 @@ orderly_location_add <- function(name, type, args, root = NULL) { ##' be unreliable. ##' ##' @export -orderly_location_add_path <- function(name, path, root = NULL) { +orderly_location_add_path <- function(name, path, verify = TRUE, root = NULL) { args <- list(path = path) - orderly_location_add(name, "path", args, root = root) + orderly_location_add(name, "path", args, verify = verify, root = root) } @@ -134,9 +146,9 @@ orderly_location_add_path <- function(name, path, root = NULL) { ##' example `http://example.com:8080` ##' ##' @export -orderly_location_add_http <- function(name, url, root = NULL) { +orderly_location_add_http <- function(name, url, verify = TRUE, root = NULL) { args <- list(url = url) - orderly_location_add(name, "http", args, root = root) + orderly_location_add(name, "http", args, verify = verify, root = root) } @@ -153,9 +165,10 @@ orderly_location_add_http <- function(name, url, root = NULL) { ##' ##' @export orderly_location_add_packit <- function(name, url, token = NULL, - save_token = NULL, root = NULL) { + save_token = NULL, + verify = TRUE, root = NULL) { args <- list(url = url, token = token, save_token = save_token) - orderly_location_add(name, "packit", args, root = root) + orderly_location_add(name, "packit", args, verify = verify, root = root) } @@ -483,6 +496,11 @@ location_driver <- function(location_name, root) { i <- match(location_name, root$config$location$name) type <- root$config$location$type[[i]] args <- root$config$location$args[[i]] + location_driver_create(type, args) +} + + +location_driver_create <- function(type, args) { location <- switch(type, path = orderly_location_path$new, http = orderly_location_http$new, @@ -794,28 +812,16 @@ location_build_push_plan <- function(packet_id, location_name, root) { ## This validation probably will need generalising in future as we add ## new types. The trick is going to be making sure that we can support ## different location types in different target languages effectively. -new_location_entry <- function(name, type, args, call = NULL) { - match_value(type, location_types, call = call) - required <- NULL - if (type == "path") { - required <- "path" - } else if (type == "http") { - required <- "url" - } else if (type == "packit") { - required <- "url" - } else if (type == "custom") { - required <- "driver" - } +new_location_entry <- function(name, type, args, call = parent.frame()) { + match_value(type, location_types) if (length(args) > 0) { - assert_is(args, "list", call = call) - assert_named(args, call = call) - } - msg <- setdiff(required, names(args)) - if (length(msg) > 0) { - cli::cli_abort("Field{?s} missing from args: {squote(msg)}") + assert_is(args, "list") + assert_named(args) } - if (type == "custom") { + if (is.null(args$driver)) { + cli::cli_abort("Field missing from args: 'driver'") + } check_symbol_from_str(args$driver, "args$driver") } diff --git a/R/location_http.R b/R/location_http.R index 60ce6ed4..2bd18a77 100644 --- a/R/location_http.R +++ b/R/location_http.R @@ -8,6 +8,22 @@ orderly_location_http <- R6::R6Class( self$client <- outpack_http_client$new(url, authorise) }, + verify = function() { + ## This should never end up triggering the assertion here as + ## http_client_handle_error() in the client will convert + ## unsuccessful requests into an error already, but this should + ## serve as a reasonable backstop. + ## + ## The act of making the request will force validation, which is + ## the most likely source of errors (along with getting the URL + ## wrong). + stopifnot(identical(self$client$request("/")$status, "success")) + }, + + authorise = function() { + self$client$authorise() + }, + list = function() { dat <- self$client$request("/metadata/list")$data data_frame( diff --git a/R/location_packit.R b/R/location_packit.R index 5f32dcd0..16f4dc7d 100644 --- a/R/location_packit.R +++ b/R/location_packit.R @@ -52,7 +52,7 @@ packit_authorisation <- function(base_url, token, save_token) { key <- rlang::hash(list(base_url = base_url, token = token)) if (is.null(auth_cache[[key]])) { - cli::cli_alert_info("Logging in to {base_url}") + cli_alert_info("Logging in to {base_url}") if (is.null(token)) { token <- do_oauth_device_flow(base_url, cache_disk = save_token) } @@ -62,7 +62,7 @@ packit_authorisation <- function(base_url, token, save_token) { login_url, function(r) http_body_json(r, list(token = scalar(token)))) - cli::cli_alert_success("Logged in successfully") + cli_alert_success("Logged in successfully") auth_cache[[key]] <- list("Authorization" = paste("Bearer", res$token)) } diff --git a/R/location_path.R b/R/location_path.R index 3cb7ea30..25177227 100644 --- a/R/location_path.R +++ b/R/location_path.R @@ -10,6 +10,10 @@ orderly_location_path <- R6::R6Class( private$root <- root_open(path, require_orderly = FALSE) }, + verify = function() { + ## No implementation needed as we verify the root in initialize() + }, + list = function() { private$root$index$location(local)[c("packet", "time", "hash")] }, diff --git a/man/orderly_location_add.Rd b/man/orderly_location_add.Rd index 82be47b9..609aa3f1 100644 --- a/man/orderly_location_add.Rd +++ b/man/orderly_location_add.Rd @@ -7,17 +7,18 @@ \alias{orderly_location_add_packit} \title{Add a new location} \usage{ -orderly_location_add(name, type, args, root = NULL) +orderly_location_add(name, type, args, verify = TRUE, root = NULL) -orderly_location_add_path(name, path, root = NULL) +orderly_location_add_path(name, path, verify = TRUE, root = NULL) -orderly_location_add_http(name, url, root = NULL) +orderly_location_add_http(name, url, verify = TRUE, root = NULL) orderly_location_add_packit( name, url, token = NULL, save_token = NULL, + verify = TRUE, root = NULL ) } @@ -33,6 +34,9 @@ API).} \item{args}{Arguments to the location driver. The arguments here will vary depending on the type used, see Details.} +\item{verify}{Logical, indicating if we should verify that the +location can be used before adding.} + \item{root}{The path to the root directory, or \code{NULL} (the default) to search for one from the current working directory. This function does not require that the directory is diff --git a/tests/testthat/test-location-packit.R b/tests/testthat/test-location-packit.R index 15f2cb23..b8f152a3 100644 --- a/tests/testthat/test-location-packit.R +++ b/tests/testthat/test-location-packit.R @@ -1,4 +1,5 @@ test_that("can authenticate with existing GitHub token", { + withr::local_options(orderly.quiet = FALSE) clear_auth_cache() withr::defer(clear_auth_cache()) @@ -43,6 +44,7 @@ test_that("can authenticate with existing Packit token", { test_that("can authenticate using device flow", { + withr::local_options(orderly.quiet = FALSE) clear_auth_cache() withr::defer(clear_auth_cache()) @@ -70,7 +72,9 @@ test_that("can authenticate using device flow", { expect_equal(args[[1]]$body$type, "json") }) + test_that("location_packit uses authentication", { + withr::local_options(orderly.quiet = FALSE) clear_auth_cache() withr::defer(clear_auth_cache()) diff --git a/tests/testthat/test-location.R b/tests/testthat/test-location.R index 572c6258..b0f2baa4 100644 --- a/tests/testthat/test-location.R +++ b/tests/testthat/test-location.R @@ -779,7 +779,7 @@ test_that("validate arguments to path locations", { expect_error( orderly_location_add("other", "path", list(root = "mypath"), root = root), - "Field missing from args: 'path'") + "'path' must be a scalar") expect_equal(orderly_location_list(root = root), "local") }) @@ -789,7 +789,7 @@ test_that("validate arguments to http locations", { expect_error( orderly_location_add("other", "http", list(server = "example.com"), root = root), - "Field missing from args: 'url'") + "'url' must be a scalar") expect_equal(orderly_location_list(root = root), "local") }) @@ -799,16 +799,22 @@ test_that("validate arguments to packit locations", { expect_error( orderly_location_add("other", "packit", list(server = "example.com"), root = root), - "Field missing from args: 'url'") + "'url' must be a scalar") expect_error( - orderly_location_add_packit("other", url = "example.com", token = 123, + orderly_location_add_packit("other", + url = "example.com", + token = 123, + verify = FALSE, root = root), "Expected 'token' to be character", fixed = TRUE) expect_error( - orderly_location_add_packit("other", "packit", url = "example.com", - save_token = "value", root = root), + orderly_location_add_packit("other", + url = "example.com", + save_token = "value", + verify = FALSE, + root = root), "Expected 'save_token' to be logical", fixed = TRUE) expect_error( @@ -816,6 +822,7 @@ test_that("validate arguments to packit locations", { url = "example.com", token = "xx", save_token = TRUE, + verify = FALSE, root = root), "Cannot specify both 'token' and 'save_token'", fixed = TRUE) @@ -825,38 +832,48 @@ test_that("validate arguments to packit locations", { test_that("can add a packit location", { root <- create_temporary_root() - orderly_location_add("other", "packit", - list(url = "https://example.com", token = "abc123"), - root = root) + orderly_location_add_packit("other", + url = "https://example.com", + token = "abc123", + verify = FALSE, + root = root) expect_equal(orderly_location_list(root = root), c("local", "other")) mock_driver <- mockery::mock() - mockery::stub(location_driver, "orderly_location_packit", mock_driver) + mockery::stub(location_driver, "location_driver_create", mock_driver) dr <- location_driver("other", root) mockery::expect_called(mock_driver, 1) expect_equal( mockery::mock_args(mock_driver)[[1]], - list(url = "https://example.com", token = "abc123")) + list("packit", + list(url = "https://example.com", + token = "abc123", + save_token = FALSE))) }) test_that("can add a packit location without a token", { root <- create_temporary_root() - orderly_location_add("other", "packit", - list(url = "https://example.com"), - root = root) + orderly_location_add_packit("other", + url = "https://example.com", + verify = FALSE, + root = root) + expect_equal( + orderly_config(root)$location$args[[2]], + list(url = "https://example.com", token = NULL, save_token = TRUE)) expect_equal(orderly_location_list(root = root), c("local", "other")) mock_driver <- mockery::mock() - mockery::stub(location_driver, "orderly_location_packit", mock_driver) + mockery::stub(location_driver, "location_driver_create", mock_driver) dr <- location_driver("other", root) mockery::expect_called(mock_driver, 1) expect_equal( mockery::mock_args(mock_driver)[[1]], - list(url = "https://example.com")) + list("packit", + list(url = "https://example.com", token = NULL, save_token = TRUE))) }) test_that("cope with trailing slash in url if needed", { @@ -917,20 +934,28 @@ test_that("can add a custom outpack location", { skip_if_not_installed("mockery") root <- create_temporary_root() args <- list(driver = "foo::bar", a = 1, b = 2) - orderly_location_add("a", "custom", args = args, root = root) + orderly_location_add("a", "custom", args = args, verify = FALSE, root = root) loc <- as.list(root$config$location[2, ]) expect_equal(loc$name, "a") expect_equal(loc$type, "custom") expect_equal(loc$args[[1]], list(driver = "foo::bar", a = 1, b = 2)) - mock_orderly_location_custom <- mockery::mock("value") - mockery::stub(location_driver, "orderly_location_custom", - mock_orderly_location_custom) + mock_orderly_location_driver_create <- mockery::mock("value") + mockery::stub(location_driver, "location_driver_create", + mock_orderly_location_driver_create) expect_equal(location_driver(loc$name, root), "value") - mockery::expect_called(mock_orderly_location_custom, 1) - expect_equal(mockery::mock_args(mock_orderly_location_custom)[[1]], - list(driver = "foo::bar", a = 1, b = 2)) + mockery::expect_called(mock_orderly_location_driver_create, 1) + expect_equal(mockery::mock_args(mock_orderly_location_driver_create)[[1]], + list("custom", list(driver = "foo::bar", a = 1, b = 2))) +}) + + +test_that("custom drivers require a 'driver' argument", { + root <- create_temporary_root() + expect_error( + orderly_location_add("a", "custom", args = list(), root = root), + "Field missing from args: 'driver'") }) @@ -1130,7 +1155,15 @@ test_that("be chatty when pulling packets", { withr::local_options(orderly.quiet = FALSE) here <- create_temporary_root() there <- create_temporary_root() - orderly_location_add_path("server", path = there$path, root = here) + res <- evaluate_promise( + orderly_location_add_path("server", path = there$path, root = here)) + expect_length(res$messages, 3) + expect_match(res$messages[[1]], + "Testing location") + expect_match(res$messages[[2]], + "Location configured successfully") + expect_match(res$messages[[3]], + "Added location 'server' (path)", fixed = TRUE) res <- evaluate_promise(orderly_location_pull_metadata(root = here)) expect_length(res$messages, 2) @@ -1160,3 +1193,18 @@ test_that("be chatty when pulling packets", { expect_match(res$messages[[2]], "Found 3 packets at 'server', of which 1 is new") }) + + +test_that("verify location on addition", { + root <- create_temporary_root() + path <- tempfile() + + expect_error( + orderly_location_add_path("upstream", path = path, root = root)) + expect_equal(orderly_location_list(root = root), "local") + + expect_no_error( + orderly_location_add_path("upstream", path = path, verify = FALSE, + root = root)) + expect_equal(orderly_location_list(root = root), c("local", "upstream")) +})