From d1f63984c40eb5a235443f60c36d01694b53bb52 Mon Sep 17 00:00:00 2001 From: Mark Edmondson Date: Tue, 11 Jan 2022 11:20:32 +0100 Subject: [PATCH 1/6] tar_resources_gcp --- R/class_resources_gcp.R | 41 +++++++++++++++++++++++++++++++++++++++++ R/tar_resources.R | 6 ++++++ R/tar_resources_gcp.R | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 R/class_resources_gcp.R create mode 100644 R/tar_resources_gcp.R diff --git a/R/class_resources_gcp.R b/R/class_resources_gcp.R new file mode 100644 index 000000000..c06e95d90 --- /dev/null +++ b/R/class_resources_gcp.R @@ -0,0 +1,41 @@ +resources_gcp_init <- function( + bucket = NULL, + prefix = path_objects_dir_cloud(), + verbose = verbose +) { + resources_gcp_new( + bucket = bucket, + prefix = prefix, + verbose = verbose + ) +} + +resources_gcp_new <- function( + bucket = NULL, + prefix = NULL, + verbose = FALSE +) { + force(bucket) + force(prefix) + force(verbose) + enclass(environment(), c("tar_resources_gcp", "tar_resources")) +} + +#' @export +resources_validate.tar_resources_gcp <- function(resources) { + tar_assert_scalar(resources$bucket %|||% "bucket") + tar_assert_chr(resources$bucket %|||% "bucket") + tar_assert_nzchar(resources$bucket %|||% "bucket") + tar_assert_scalar(resources$prefix) + tar_assert_chr(resources$prefix) + tar_assert_nzchar(resources$prefix) + tar_assert_lgl(resources$verbose) +} + +#' @export +print.tar_resources_gcp <- function(x, ...) { + cat( + "\n ", + paste0(paste_list(as.list(x)), collapse = "\n ") + ) +} diff --git a/R/tar_resources.R b/R/tar_resources.R index 826764757..343fd61b0 100644 --- a/R/tar_resources.R +++ b/R/tar_resources.R @@ -46,6 +46,11 @@ #' argument of `future::future()` for `targets`. #' Resources supplied through #' `future::plan()` and `future::tweak()` are completely ignored. +#' @param gcp Output of function `tar_resources_gcp()`. +#' Google Cloud Platform bucket storage settings for GCP backed storage formats +#' such as `"gcp_qs"` and `"gcp_parquet`. Applies to all formats +#' beginning with the `"gcp_"` prefix. For details on formats, +#' see the `format` argument of [tar_target()]. #' @param parquet Output of function `tar_resources_parquet()`. #' Non-default arguments to `arrow::read_parquet()` and #' `arrow::write_parquet()` for `arrow`/parquet-based storage formats. @@ -78,6 +83,7 @@ tar_resources <- function( feather = NULL, fst = NULL, future = NULL, + gcp = NULL, parquet = NULL, qs = NULL, url = NULL diff --git a/R/tar_resources_gcp.R b/R/tar_resources_gcp.R new file mode 100644 index 000000000..501c5e0a7 --- /dev/null +++ b/R/tar_resources_gcp.R @@ -0,0 +1,39 @@ +#' @title Target resources: GCP storage formats +#' @export +#' @family resources +#' @description Create the `gcp` argument of `tar_resources()` +#' to specify optional settings to gcp storage formats. +#' See the `format` argument of [tar_target()] for details. +#' @inheritSection tar_resources Resources +#' @return Object of class `"tar_resources_gcp"`, to be supplied +#' to the `gcp` argument of `tar_resources()`. +#' @param bucket Character of length 1, name of an existing +#' gcp bucket to upload and download the return values +#' of the affected targets during the pipeline. +#' @param prefix Character of length 1, "directory path" +#' in the S3 bucket where the target return values are stored. +#' @param verbose Whether to have feedback on the GCS upload process +#' @examples +#' # Somewhere in you target script file (usually _targets.R): +#' tar_target( +#' name, +#' command(), +#' format = "gcp_qs", +#' resources = tar_resources( +#' gcp = tar_resources_gcp(bucket = "yourbucketname"), +#' qs = tar_resources_qs(preset = "fast") +#' ) +#' ) +tar_resources_gcp <- function( + bucket, + prefix = targets::path_objects_dir_cloud(), + verbose = FALSE +) { + out <- resources_gcp_init( + bucket = bucket, + prefix = prefix, + verbose = verbose + ) + resources_validate(out) + out +} From 3ddc18310a33d47da5a25980a20b1ef503daeb15 Mon Sep 17 00:00:00 2001 From: Mark Edmondson Date: Tue, 11 Jan 2022 11:35:32 +0100 Subject: [PATCH 2/6] add docs and test --- NAMESPACE | 3 + R/class_resources_gcp.R | 2 +- man/tar_resources.Rd | 8 +++ man/tar_resources_aws.Rd | 1 + man/tar_resources_clustermq.Rd | 1 + man/tar_resources_feather.Rd | 1 + man/tar_resources_fst.Rd | 1 + man/tar_resources_future.Rd | 1 + man/tar_resources_gcp.Rd | 71 +++++++++++++++++++++++ man/tar_resources_parquet.Rd | 1 + man/tar_resources_qs.Rd | 1 + man/tar_resources_url.Rd | 1 + targets.Rproj | 1 + tests/testthat/test-class_resources_gcp.R | 15 +++++ tests/testthat/test-tar_resources.R | 4 +- 15 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 man/tar_resources_gcp.Rd create mode 100644 tests/testthat/test-class_resources_gcp.R diff --git a/NAMESPACE b/NAMESPACE index 7f5e5cbef..6fd569add 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -19,6 +19,7 @@ S3method(print,tar_resources_clustermq) S3method(print,tar_resources_feather) S3method(print,tar_resources_fst) S3method(print,tar_resources_future) +S3method(print,tar_resources_gcp) S3method(print,tar_resources_parquet) S3method(print,tar_resources_qs) S3method(print,tar_resources_url) @@ -29,6 +30,7 @@ S3method(resources_validate,tar_resources_clustermq) S3method(resources_validate,tar_resources_feather) S3method(resources_validate,tar_resources_fst) S3method(resources_validate,tar_resources_future) +S3method(resources_validate,tar_resources_gcp) S3method(resources_validate,tar_resources_parquet) S3method(resources_validate,tar_resources_qs) S3method(resources_validate,tar_resources_url) @@ -398,6 +400,7 @@ export(tar_resources_clustermq) export(tar_resources_feather) export(tar_resources_fst) export(tar_resources_future) +export(tar_resources_gcp) export(tar_resources_parquet) export(tar_resources_qs) export(tar_resources_url) diff --git a/R/class_resources_gcp.R b/R/class_resources_gcp.R index c06e95d90..b243a31d4 100644 --- a/R/class_resources_gcp.R +++ b/R/class_resources_gcp.R @@ -1,7 +1,7 @@ resources_gcp_init <- function( bucket = NULL, prefix = path_objects_dir_cloud(), - verbose = verbose + verbose = FALSE ) { resources_gcp_new( bucket = bucket, diff --git a/man/tar_resources.Rd b/man/tar_resources.Rd index 9c60b5f29..91d8073c4 100644 --- a/man/tar_resources.Rd +++ b/man/tar_resources.Rd @@ -10,6 +10,7 @@ tar_resources( feather = NULL, fst = NULL, future = NULL, + gcp = NULL, parquet = NULL, qs = NULL, url = NULL @@ -49,6 +50,12 @@ argument of \code{future::future()} for \code{targets}. Resources supplied through \code{future::plan()} and \code{future::tweak()} are completely ignored.} +\item{gcp}{Output of function \code{tar_resources_gcp()}. +Google Cloud Platform bucket storage settings for GCP backed storage formats +such as \code{"gcp_qs"} and \verb{"gcp_parquet}. Applies to all formats +beginning with the \code{"gcp_"} prefix. For details on formats, +see the \code{format} argument of \code{\link[=tar_target]{tar_target()}}.} + \item{parquet}{Output of function \code{tar_resources_parquet()}. Non-default arguments to \code{arrow::read_parquet()} and \code{arrow::write_parquet()} for \code{arrow}/parquet-based storage formats. @@ -110,6 +117,7 @@ Other resources: \code{\link{tar_resources_feather}()}, \code{\link{tar_resources_fst}()}, \code{\link{tar_resources_future}()}, +\code{\link{tar_resources_gcp}()}, \code{\link{tar_resources_parquet}()}, \code{\link{tar_resources_qs}()}, \code{\link{tar_resources_url}()} diff --git a/man/tar_resources_aws.Rd b/man/tar_resources_aws.Rd index 90d831e98..5fc18a05d 100644 --- a/man/tar_resources_aws.Rd +++ b/man/tar_resources_aws.Rd @@ -69,6 +69,7 @@ Other resources: \code{\link{tar_resources_feather}()}, \code{\link{tar_resources_fst}()}, \code{\link{tar_resources_future}()}, +\code{\link{tar_resources_gcp}()}, \code{\link{tar_resources_parquet}()}, \code{\link{tar_resources_qs}()}, \code{\link{tar_resources_url}()}, diff --git a/man/tar_resources_clustermq.Rd b/man/tar_resources_clustermq.Rd index 2e385dbb6..46e0764eb 100644 --- a/man/tar_resources_clustermq.Rd +++ b/man/tar_resources_clustermq.Rd @@ -52,6 +52,7 @@ Other resources: \code{\link{tar_resources_feather}()}, \code{\link{tar_resources_fst}()}, \code{\link{tar_resources_future}()}, +\code{\link{tar_resources_gcp}()}, \code{\link{tar_resources_parquet}()}, \code{\link{tar_resources_qs}()}, \code{\link{tar_resources_url}()}, diff --git a/man/tar_resources_feather.Rd b/man/tar_resources_feather.Rd index 76105a514..e08c81969 100644 --- a/man/tar_resources_feather.Rd +++ b/man/tar_resources_feather.Rd @@ -55,6 +55,7 @@ Other resources: \code{\link{tar_resources_clustermq}()}, \code{\link{tar_resources_fst}()}, \code{\link{tar_resources_future}()}, +\code{\link{tar_resources_gcp}()}, \code{\link{tar_resources_parquet}()}, \code{\link{tar_resources_qs}()}, \code{\link{tar_resources_url}()}, diff --git a/man/tar_resources_fst.Rd b/man/tar_resources_fst.Rd index 47dfc45ee..5619ac6ab 100644 --- a/man/tar_resources_fst.Rd +++ b/man/tar_resources_fst.Rd @@ -52,6 +52,7 @@ Other resources: \code{\link{tar_resources_clustermq}()}, \code{\link{tar_resources_feather}()}, \code{\link{tar_resources_future}()}, +\code{\link{tar_resources_gcp}()}, \code{\link{tar_resources_parquet}()}, \code{\link{tar_resources_qs}()}, \code{\link{tar_resources_url}()}, diff --git a/man/tar_resources_future.Rd b/man/tar_resources_future.Rd index 50862d1f8..ddac7dd81 100644 --- a/man/tar_resources_future.Rd +++ b/man/tar_resources_future.Rd @@ -63,6 +63,7 @@ Other resources: \code{\link{tar_resources_clustermq}()}, \code{\link{tar_resources_feather}()}, \code{\link{tar_resources_fst}()}, +\code{\link{tar_resources_gcp}()}, \code{\link{tar_resources_parquet}()}, \code{\link{tar_resources_qs}()}, \code{\link{tar_resources_url}()}, diff --git a/man/tar_resources_gcp.Rd b/man/tar_resources_gcp.Rd new file mode 100644 index 000000000..45fb51855 --- /dev/null +++ b/man/tar_resources_gcp.Rd @@ -0,0 +1,71 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/tar_resources_gcp.R +\name{tar_resources_gcp} +\alias{tar_resources_gcp} +\title{Target resources: GCP storage formats} +\usage{ +tar_resources_gcp( + bucket, + prefix = targets::path_objects_dir_cloud(), + verbose = FALSE +) +} +\arguments{ +\item{bucket}{Character of length 1, name of an existing +gcp bucket to upload and download the return values +of the affected targets during the pipeline.} + +\item{prefix}{Character of length 1, "directory path" +in the S3 bucket where the target return values are stored.} + +\item{verbose}{Whether to have feedback on the GCS upload process} +} +\value{ +Object of class \code{"tar_resources_gcp"}, to be supplied +to the \code{gcp} argument of \code{tar_resources()}. +} +\description{ +Create the \code{gcp} argument of \code{tar_resources()} +to specify optional settings to gcp storage formats. +See the \code{format} argument of \code{\link[=tar_target]{tar_target()}} for details. +} +\section{Resources}{ + +Functions \code{\link[=tar_target]{tar_target()}} and \code{\link[=tar_option_set]{tar_option_set()}} +each takes an optional \code{resources} argument to supply +non-default settings of various optional backends for data storage +and high-performance computing. The \code{tar_resources()} function +is a helper to supply those settings in the correct manner. +Resources are all-or-nothing: if you specify any resources +with \code{\link[=tar_target]{tar_target()}}, all the resources from \code{tar_option_get("resources")} +are dropped for that target. In other words, if you write +\code{tar_option_set(resources = resources_1)} and then +\code{tar_target(x, my_command(), resources = resources_2)}, then everything +in \code{resources_1} is discarded for target \code{x}. +} + +\examples{ +# Somewhere in you target script file (usually _targets.R): +tar_target( + name, + command(), + format = "gcp_qs", + resources = tar_resources( + gcp = tar_resources_gcp(bucket = "yourbucketname"), + qs = tar_resources_qs(preset = "fast") + ) +) +} +\seealso{ +Other resources: +\code{\link{tar_resources_aws}()}, +\code{\link{tar_resources_clustermq}()}, +\code{\link{tar_resources_feather}()}, +\code{\link{tar_resources_fst}()}, +\code{\link{tar_resources_future}()}, +\code{\link{tar_resources_parquet}()}, +\code{\link{tar_resources_qs}()}, +\code{\link{tar_resources_url}()}, +\code{\link{tar_resources}()} +} +\concept{resources} diff --git a/man/tar_resources_parquet.Rd b/man/tar_resources_parquet.Rd index c15c5687e..0ad7d9dc4 100644 --- a/man/tar_resources_parquet.Rd +++ b/man/tar_resources_parquet.Rd @@ -53,6 +53,7 @@ Other resources: \code{\link{tar_resources_feather}()}, \code{\link{tar_resources_fst}()}, \code{\link{tar_resources_future}()}, +\code{\link{tar_resources_gcp}()}, \code{\link{tar_resources_qs}()}, \code{\link{tar_resources_url}()}, \code{\link{tar_resources}()} diff --git a/man/tar_resources_qs.Rd b/man/tar_resources_qs.Rd index e0dcc8180..7af9be88c 100644 --- a/man/tar_resources_qs.Rd +++ b/man/tar_resources_qs.Rd @@ -53,6 +53,7 @@ Other resources: \code{\link{tar_resources_feather}()}, \code{\link{tar_resources_fst}()}, \code{\link{tar_resources_future}()}, +\code{\link{tar_resources_gcp}()}, \code{\link{tar_resources_parquet}()}, \code{\link{tar_resources_url}()}, \code{\link{tar_resources}()} diff --git a/man/tar_resources_url.Rd b/man/tar_resources_url.Rd index e939c54f4..8b757023b 100644 --- a/man/tar_resources_url.Rd +++ b/man/tar_resources_url.Rd @@ -53,6 +53,7 @@ Other resources: \code{\link{tar_resources_feather}()}, \code{\link{tar_resources_fst}()}, \code{\link{tar_resources_future}()}, +\code{\link{tar_resources_gcp}()}, \code{\link{tar_resources_parquet}()}, \code{\link{tar_resources_qs}()}, \code{\link{tar_resources}()} diff --git a/targets.Rproj b/targets.Rproj index 21a4da087..eaa6b8186 100644 --- a/targets.Rproj +++ b/targets.Rproj @@ -15,3 +15,4 @@ LaTeX: pdfLaTeX BuildType: Package PackageUseDevtools: Yes PackageInstallArgs: --no-multiarch --with-keep.source +PackageRoxygenize: rd,collate,namespace diff --git a/tests/testthat/test-class_resources_gcp.R b/tests/testthat/test-class_resources_gcp.R new file mode 100644 index 000000000..392f48c9a --- /dev/null +++ b/tests/testthat/test-class_resources_gcp.R @@ -0,0 +1,15 @@ +tar_test("create tar_resources_gcp object", { + x <- resources_gcp_init(bucket = "bucket_name") + expect_silent(resources_validate(x)) +}) + +tar_test("prohibit empty tar_resources_gcp object", { + x <- resources_gcp_init(bucket = "", prefix = NULL) + expect_error(resources_validate(x), class = "tar_condition_validate") +}) + +tar_test("print tar_resources_aws object", { + x <- resources_gcp_init(bucket = "bucket_name") + out <- utils::capture.output(print(x)) + expect_true(any(grepl("tar_resources_gcp", out))) +}) diff --git a/tests/testthat/test-tar_resources.R b/tests/testthat/test-tar_resources.R index 9661453b1..329a0eafa 100644 --- a/tests/testthat/test-tar_resources.R +++ b/tests/testthat/test-tar_resources.R @@ -5,13 +5,15 @@ tar_test("empty resources", { tar_test("populated resources", { out <- tar_resources( aws = resources_aws_init(), + gcp = resources_gcp_init(), clustermq = resources_clustermq_init( template = list(a = 1, n_cores = 123) ) ) expect_true(is.list(out)) - expect_equal(sort(names(out)), sort(c("aws", "clustermq"))) + expect_equal(sort(names(out)), sort(c("aws", "clustermq", "gcp"))) expect_true(inherits(out$aws, "tar_resources_aws")) + expect_true(inherits(out$gcp, "tar_resources_gcp")) expect_true(inherits(out$clustermq, "tar_resources_clustermq")) }) From 4f51ac5e17d1c46694553465cb9eed664c0b1ff5 Mon Sep 17 00:00:00 2001 From: Mark Edmondson Date: Tue, 11 Jan 2022 13:22:33 +0100 Subject: [PATCH 3/6] lint --- R/tar_resources.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/tar_resources.R b/R/tar_resources.R index 343fd61b0..43e73e0d6 100644 --- a/R/tar_resources.R +++ b/R/tar_resources.R @@ -47,8 +47,8 @@ #' Resources supplied through #' `future::plan()` and `future::tweak()` are completely ignored. #' @param gcp Output of function `tar_resources_gcp()`. -#' Google Cloud Platform bucket storage settings for GCP backed storage formats -#' such as `"gcp_qs"` and `"gcp_parquet`. Applies to all formats +#' Google Cloud Platform bucket storage settings for GCP backed storage +#' formats such as `"gcp_qs"` and `"gcp_parquet`. Applies to all formats #' beginning with the `"gcp_"` prefix. For details on formats, #' see the `format` argument of [tar_target()]. #' @param parquet Output of function `tar_resources_parquet()`. From c36aa63767f7375265dfa8725875cddce2274e60 Mon Sep 17 00:00:00 2001 From: Mark Edmondson Date: Sun, 16 Jan 2022 07:08:41 +0100 Subject: [PATCH 4/6] fix feedback, pass lints --- R/class_resources_gcp.R | 1 + R/tar_resources.R | 2 +- R/tar_resources_gcp.R | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/R/class_resources_gcp.R b/R/class_resources_gcp.R index b243a31d4..d320267f6 100644 --- a/R/class_resources_gcp.R +++ b/R/class_resources_gcp.R @@ -30,6 +30,7 @@ resources_validate.tar_resources_gcp <- function(resources) { tar_assert_chr(resources$prefix) tar_assert_nzchar(resources$prefix) tar_assert_lgl(resources$verbose) + tar_assert_scalar(resources$verbose) } #' @export diff --git a/R/tar_resources.R b/R/tar_resources.R index 43e73e0d6..b31c18c14 100644 --- a/R/tar_resources.R +++ b/R/tar_resources.R @@ -47,7 +47,7 @@ #' Resources supplied through #' `future::plan()` and `future::tweak()` are completely ignored. #' @param gcp Output of function `tar_resources_gcp()`. -#' Google Cloud Platform bucket storage settings for GCP backed storage +#' Google Cloud Platform bucket storage settings for GCP backed storage #' formats such as `"gcp_qs"` and `"gcp_parquet`. Applies to all formats #' beginning with the `"gcp_"` prefix. For details on formats, #' see the `format` argument of [tar_target()]. diff --git a/R/tar_resources_gcp.R b/R/tar_resources_gcp.R index 501c5e0a7..21f28c98a 100644 --- a/R/tar_resources_gcp.R +++ b/R/tar_resources_gcp.R @@ -1,4 +1,4 @@ -#' @title Target resources: GCP storage formats +#' @title Target resources: Google Cloud Platform storage formats #' @export #' @family resources #' @description Create the `gcp` argument of `tar_resources()` @@ -15,6 +15,7 @@ #' @param verbose Whether to have feedback on the GCS upload process #' @examples #' # Somewhere in you target script file (usually _targets.R): +#' if (identical(Sys.getenv("TAR_EXAMPLES"), "true")) { #' tar_target( #' name, #' command(), @@ -24,6 +25,7 @@ #' qs = tar_resources_qs(preset = "fast") #' ) #' ) +#' } tar_resources_gcp <- function( bucket, prefix = targets::path_objects_dir_cloud(), From 36a833b324f422b6ab30bd878b771aa65b498bc4 Mon Sep 17 00:00:00 2001 From: Mark Edmondson Date: Mon, 17 Jan 2022 09:12:11 +0100 Subject: [PATCH 5/6] Rd genereation --- man/tar_resources.Rd | 4 ++-- man/tar_resources_gcp.Rd | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/man/tar_resources.Rd b/man/tar_resources.Rd index 91d8073c4..6c8096ae9 100644 --- a/man/tar_resources.Rd +++ b/man/tar_resources.Rd @@ -51,8 +51,8 @@ Resources supplied through \code{future::plan()} and \code{future::tweak()} are completely ignored.} \item{gcp}{Output of function \code{tar_resources_gcp()}. -Google Cloud Platform bucket storage settings for GCP backed storage formats -such as \code{"gcp_qs"} and \verb{"gcp_parquet}. Applies to all formats +Google Cloud Platform bucket storage settings for GCP backed storage +formats such as \code{"gcp_qs"} and \verb{"gcp_parquet}. Applies to all formats beginning with the \code{"gcp_"} prefix. For details on formats, see the \code{format} argument of \code{\link[=tar_target]{tar_target()}}.} diff --git a/man/tar_resources_gcp.Rd b/man/tar_resources_gcp.Rd index 45fb51855..3abb16209 100644 --- a/man/tar_resources_gcp.Rd +++ b/man/tar_resources_gcp.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/tar_resources_gcp.R \name{tar_resources_gcp} \alias{tar_resources_gcp} -\title{Target resources: GCP storage formats} +\title{Target resources: Google Cloud Platform storage formats} \usage{ tar_resources_gcp( bucket, @@ -46,6 +46,7 @@ in \code{resources_1} is discarded for target \code{x}. \examples{ # Somewhere in you target script file (usually _targets.R): +if (identical(Sys.getenv("TAR_EXAMPLES"), "true")) { tar_target( name, command(), @@ -56,6 +57,7 @@ tar_target( ) ) } +} \seealso{ Other resources: \code{\link{tar_resources_aws}()}, From 247cbcf3b54825d66165c9d116821c995679bb5c Mon Sep 17 00:00:00 2001 From: Mark Edmondson Date: Wed, 19 Jan 2022 16:14:01 +0100 Subject: [PATCH 6/6] add test for tar_resources_gcp --- tests/testthat/test-tar_resources_gcp.R | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/testthat/test-tar_resources_gcp.R diff --git a/tests/testthat/test-tar_resources_gcp.R b/tests/testthat/test-tar_resources_gcp.R new file mode 100644 index 000000000..2836c9ed4 --- /dev/null +++ b/tests/testthat/test-tar_resources_gcp.R @@ -0,0 +1,6 @@ +tar_test("tar_resources_gcp()", { + out <- tar_resources_gcp(bucket = "bucket123") + expect_equal(out$bucket, "bucket123") + expect_null(out$region) + expect_silent(resources_validate(out)) +})