This commit is contained in:
Hong Ooi 2019-06-02 02:06:34 +10:00
Родитель eff68585d8
Коммит 417db355e2
13 изменённых файлов: 490 добавлений и 43 удалений

Просмотреть файл

@ -1,12 +1,12 @@
Package: AzureRMR
Title: Interface to 'Azure Resource Manager'
Version: 2.1.1.9000
Version: 2.1.2
Authors@R: c(
person("Hong", "Ooi", , "hongooi@microsoft.com", role = c("aut", "cre")),
person("Microsoft", role="cph")
)
Description: A lightweight but powerful R interface to the 'Azure Resource Manager' REST API. The package exposes classes and methods for 'OAuth' authentication and working with subscriptions and resource groups. It also provides functionality for creating and deleting 'Azure' resources and deploying templates. While 'AzureRMR' can be used to manage any 'Azure' service, it can also be extended by other packages to provide extra functionality for specific services.
URL: https://github.com/Azure/AzureRMR
Description: A lightweight but powerful R interface to the 'Azure Resource Manager' REST API. The package exposes classes and methods for 'OAuth' authentication and working with subscriptions and resource groups. It also provides functionality for creating and deleting 'Azure' resources and deploying templates. While 'AzureRMR' can be used to manage any 'Azure' service, it can also be extended by other packages to provide extra functionality for specific services. Part of the 'AzureR' family of packages.
URL: https://github.com/Azure/AzureRMR https://github.com/Azure/AzureR
BugReports: https://github.com/Azure/AzureRMR/issues
License: MIT + file LICENSE
VignetteBuilder: knitr

Просмотреть файл

@ -1,5 +1,7 @@
# Generated by roxygen2: do not edit by hand
S3method(build_template_definition,default)
S3method(build_template_parameters,default)
export(AzureR_dir)
export(az_resource)
export(az_resource_group)
@ -8,6 +10,8 @@ export(az_role_assignment)
export(az_role_definition)
export(az_subscription)
export(az_template)
export(build_template_definition)
export(build_template_parameters)
export(call_azure_rm)
export(call_azure_url)
export(clean_token_directory)

Просмотреть файл

@ -1,6 +1,7 @@
# AzureRMR 2.1.1.9000
# AzureRMR 2.1.2
- Fix a bug in template deployment where null fields were not handled correctly.
- New `build_template_definition` and `build_parameters_parameters` generics to help in template deployment. These can take as inputs R lists, JSON text strings, or file connections, and can also be extended by other packages.
# AzureRMR 2.1.1

Просмотреть файл

@ -28,8 +28,10 @@
#' - `parameters`: The parameters for the template. This can be provided using any of the same methods as the `template` argument.
#' - `wait`: Optionally, whether to wait until the deployment is complete. Defaults to FALSE, in which case the method will return immediately.
#'
#' You can use the `build_template_definition` and `build_template_parameters` helper functions to construct the inputs for deploying a template. These can take as inputs R lists, JSON text strings, or file connections, and can also be extended by other packages.
#'
#' @seealso
#' [az_resource_group], [az_resource],
#' [az_resource_group], [az_resource], [build_template_definition], [build_template_parameters]
#' [Template overview](https://docs.microsoft.com/en-us/azure/templates/),
#' [Template API reference](https://docs.microsoft.com/en-us/rest/api/resources/deployments)
#'
@ -200,6 +202,8 @@ private=list(
# fold template data into properties
properties <- if(is.list(template))
append_json(properties, template=generate_json(template))
else if(is_file_spec(template))
append_json(properties, template=readLines(template))
else if(is_url(template))
append_json(properties, templateLink=generate_json(list(uri=template)))
else append_json(properties, template=template)
@ -207,13 +211,15 @@ private=list(
# handle case of missing or empty parameters arg
# must be a _named_ list for jsonlite to turn into an object, not an array
if(missing(parameters) || is_empty(parameters))
parameters <- structure(list(), names=character(0))
parameters <- named_list()
# fold parameter data into properties
properties <- if(is_empty(parameters))
append_json(properties, parameters=generate_json(parameters))
else if(is.list(parameters))
append_json(properties, parameters=generate_json(private$make_param_list(parameters)))
append_json(properties, parameters=do.call(build_template_parameters, parameters))
else if(is_file_spec(parameters))
append_json(properties, parameters=readLines(parameters))
else if(is_url(parameters))
append_json(properties, parametersLink=generate_json(list(uri=parameters)))
else append_json(properties, parameters=parameters)
@ -279,12 +285,6 @@ private=list(
}
},
# params for templates require lists of (value=x) rather than vectors as inputs
make_param_list=function(params)
{
lapply(params, function(x) if(is.list(x)) x else list(value=x))
},
tpl_op=function(op="", ...)
{
op <- construct_path("resourcegroups", self$resource_group,
@ -293,29 +293,3 @@ private=list(
}
))
generate_json <- function(object)
{
jsonlite::toJSON(object, pretty=TRUE, auto_unbox=TRUE, null="null", digits=22)
}
append_json <- function(props, ...)
{
lst <- list(...)
lst_names <- names(lst)
if(is.null(lst_names) || any(lst_names == ""))
stop("Deployment properties must be named", call.=FALSE)
for(i in seq_along(lst))
{
lst_i <- lst[[i]]
if(inherits(lst_i, "connection") || (length(lst_i) == 1 && file.exists(lst_i)))
lst_i <- readLines(lst_i)
newprop <- sprintf(', "%s": %s}', lst_names[i], paste0(lst_i, collapse="\n"))
props <- sub("\\}$", newprop, props)
}
props
}

218
R/build_tpl_json.R Normal file
Просмотреть файл

@ -0,0 +1,218 @@
#' Build the JSON for a template and its parameters
#'
#' @param ... For `build_template_parameters`, named arguments giving the values of each template parameter. For `build_template_definition`, further arguments passed to class methods.
#' @param parameters For `build_template_definition`, the parameter names and types for the template. See 'Details' below.
#' @param variables Internal variables used by the template.
#' @param resources List of resources that the template should deploy.
#' @param outputs The template outputs.
#'
#' @details
#' `build_template_definition` is used to generate a template from its components. The arguments can be specified in various ways:
#' - As character strings containing unparsed JSON text.
#' - As an R list of (nested) objects representing the parsed JSON.
#' - A connection pointing to a JSON file or object.
#' - For the `parameters` argument, this can also be a character vector containing the types of each parameter.
#'
#' `build_template_parameters` is for creating the list of parameters to be passed along with the template. Its arguments should all be named, and contain either the JSON text or an R list giving the parsed JSON.
#'
#' Both of these are generics and can be extended by other packages to handle specific deployment scenarios, eg virtual machines.
#'
#' @return
#' The JSON text for the template definition and its parameters.
#'
#' @examples
#' # dummy example
#' # note that 'resources' arg should be a _list_ of resources
#' build_template_definition(resources=list(list(name="resource here")))
#'
#' # specifying parameters as a list
#' build_template_definition(parameters=list(par1=list(type="string")),
#' resources=list(list(name="resource here")))
#'
#' # specifying parameters as a vector
#' build_template_definition(parameters=c(par1="string"),
#' resources=list(list(name="resource here")))
#'
#' # realistic example: storage account
#' build_template_definition(
#' parameters=c(
#' name="string",
#' location="string",
#' sku="string"
#' ),
#' variables=list(
#' id="[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]"
#' ),
#' resources=list(
#' list(
#' name="[parameters('name')]",
#' location="[parameters('location')]",
#' type="Microsoft.Storage/storageAccounts",
#' apiVersion="2018-07-01",
#' sku=list(
#' name="[parameters('sku')]"
#' ),
#' kind="Storage"
#' )
#' ),
#' outputs=list(
#' storageId="[variables('id')]"
#' )
#' )
#'
#' # providing JSON text as input
#' build_template_definition(
#' parameters=c(name="string", location="string", sku="string"),
#' resources='[
#' {
#' "name": "[parameters(\'name\')]",
#' "location": "[parameters(\'location\')]",
#' "type": "Microsoft.Storage/storageAccounts",
#' "apiVersion": "2018-07-01",
#' "sku": {
#' "name": "[parameters(\'sku\')]"
#' },
#' "kind": "Storage"
#' }
#' ]'
#' )
#'
#' # parameter values
#' build_template_parameters(name="mystorageacct", location="westus", sku="Standard_LRS")
#'
#' build_template_parameters(
#' param='{
#' "name": "myname",
#' "properties": { "prop1": 42, "prop2": "hello" }
#' }'
#' )
#'
#' param_json <- '{
#' "name": "myname",
#' "properties": { "prop1": 42, "prop2": "hello" }
#' }'
#' build_template_parameters(param=textConnection(param_json))
#'
#' \dontrun{
#' # reading JSON definitions from a file
#' build_template_definition(
#' parameters=file("parameter_def.json"),
#' resources=file("resource_def.json")
#'
#' build_template_parameters(name="myres_name", complex_type=file("myres_params.json"))
#' )
#' }
#'
#' @rdname build_template
#' @aliases build_template
#' @export
build_template_definition <- function(...)
{
UseMethod("build_template_definition")
}
#' @rdname build_template
#' @export
build_template_definition.default <- function(parameters=NULL, variables=NULL, resources=NULL, outputs=NULL, ...)
{
# special treatment for parameters arg: convert 'c(name="type")' to 'list(name=list(type="type"))'
if(is.character(parameters))
parameters <- sapply(parameters, function(type) list(type=type), simplify=FALSE)
parts <- lapply(
list(parameters=parameters, variables=variables, resources=resources, outputs=outputs, ...),
function(x)
{
if(inherits(x, "connection"))
{
on.exit(close(x))
readLines(x)
}
else generate_json(if(is.null(x)) named_list() else x)
}
)
json <- generate_json(list(
`$schema`="http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
contentVersion="1.0.0.0"
))
for(i in seq_along(parts))
json <- do.call(append_json, c(json, parts[i]))
jsonlite::prettify(json)
}
#' @rdname build_template
#' @export
build_template_parameters <- function(...)
{
UseMethod("build_template_parameters")
}
#' @rdname build_template
#' @export
build_template_parameters.default <- function(...)
{
dots <- list(...)
# handle no-parameter case
if(is_empty(dots))
return("{}")
parms <- lapply(dots, function(value)
{
# need to duplicate functionality of generate_json, one level down
if(inherits(value, "connection"))
{
on.exit(close(value))
generate_json(list(value=jsonlite::fromJSON(readLines(value), simplifyVector=FALSE)))
}
else if(is.character(value) && jsonlite::validate(value))
generate_json(list(value=jsonlite::fromJSON(value, simplifyVector=FALSE)))
else generate_json(list(value=value))
})
jsonlite::prettify(do.call(append_json, c(list("{}"), parms)))
}
generate_json <- function(object)
{
if(is.character(object) && jsonlite::validate(object))
object
else jsonlite::toJSON(object, auto_unbox=TRUE, null="null", digits=22)
}
append_json <- function(props, ...)
{
lst <- list(...)
lst_names <- names(lst)
if(is.null(lst_names) || any(lst_names == ""))
stop("Deployment properties and parameters must be named", call.=FALSE)
for(i in seq_along(lst))
{
lst_i <- lst[[i]]
if(inherits(lst_i, "connection"))
{
on.exit(close(lst_i))
lst_i <- readLines(lst_i)
}
newprop <- sprintf('"%s": %s}', lst_names[i], paste0(lst_i, collapse="\n"))
if(!grepl("^\\{[[:space:]]*\\}$", props))
newprop <- paste(",", newprop)
props <- sub("\\}$", newprop, props)
}
props
}
is_file_spec <- function(x)
{
inherits(x, "connection") || (is.character(x) && length(x) == 1 && file.exists(x))
}

Просмотреть файл

@ -46,6 +46,8 @@ If you also supply the following arguments to \code{new()}, a new template will
\item \code{parameters}: The parameters for the template. This can be provided using any of the same methods as the \code{template} argument.
\item \code{wait}: Optionally, whether to wait until the deployment is complete. Defaults to FALSE, in which case the method will return immediately.
}
You can use the \code{build_template_definition} and \code{build_template_parameters} helper functions to construct the inputs for deploying a template. These can take as inputs R lists, JSON text strings, or file connections, and can also be extended by other packages.
}
\examples{
@ -69,7 +71,7 @@ tpl$delete(free_resources=TRUE)
}
}
\seealso{
\link{az_resource_group}, \link{az_resource},
\link{az_resource_group}, \link{az_resource}, \link{build_template_definition}, \link{build_template_parameters}
\href{https://docs.microsoft.com/en-us/azure/templates/}{Template overview},
\href{https://docs.microsoft.com/en-us/rest/api/resources/deployments}{Template API reference}
}

133
man/build_template.Rd Normal file
Просмотреть файл

@ -0,0 +1,133 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/build_tpl_json.R
\name{build_template_definition}
\alias{build_template_definition}
\alias{build_template}
\alias{build_template_definition.default}
\alias{build_template_parameters}
\alias{build_template_parameters.default}
\title{Build the JSON for a template and its parameters}
\usage{
build_template_definition(...)
\method{build_template_definition}{default}(parameters = NULL,
variables = NULL, resources = NULL, outputs = NULL, ...)
build_template_parameters(...)
\method{build_template_parameters}{default}(...)
}
\arguments{
\item{...}{For \code{build_template_parameters}, named arguments giving the values of each template parameter. For \code{build_template_definition}, further arguments passed to class methods.}
\item{parameters}{For \code{build_template_definition}, the parameter names and types for the template. See 'Details' below.}
\item{variables}{Internal variables used by the template.}
\item{resources}{List of resources that the template should deploy.}
\item{outputs}{The template outputs.}
}
\value{
The JSON text for the template definition and its parameters.
}
\description{
Build the JSON for a template and its parameters
}
\details{
\code{build_template_definition} is used to generate a template from its components. The arguments can be specified in various ways:
\itemize{
\item As character strings containing unparsed JSON text.
\item As an R list of (nested) objects representing the parsed JSON.
\item A connection pointing to a JSON file or object.
\item For the \code{parameters} argument, this can also be a character vector containing the types of each parameter.
}
\code{build_template_parameters} is for creating the list of parameters to be passed along with the template. Its arguments should all be named, and contain either the JSON text or an R list giving the parsed JSON.
Both of these are generics and can be extended by other packages to handle specific deployment scenarios, eg virtual machines.
}
\examples{
# dummy example
# note that 'resources' arg should be a _list_ of resources
build_template_definition(resources=list(list(name="resource here")))
# specifying parameters as a list
build_template_definition(parameters=list(par1=list(type="string")),
resources=list(list(name="resource here")))
# specifying parameters as a vector
build_template_definition(parameters=c(par1="string"),
resources=list(list(name="resource here")))
# realistic example: storage account
build_template_definition(
parameters=c(
name="string",
location="string",
sku="string"
),
variables=list(
id="[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]"
),
resources=list(
list(
name="[parameters('name')]",
location="[parameters('location')]",
type="Microsoft.Storage/storageAccounts",
apiVersion="2018-07-01",
sku=list(
name="[parameters('sku')]"
),
kind="Storage"
)
),
outputs=list(
storageId="[variables('id')]"
)
)
# providing JSON text as input
build_template_definition(
parameters=c(name="string", location="string", sku="string"),
resources='[
{
"name": "[parameters(\\'name\\')]",
"location": "[parameters(\\'location\\')]",
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2018-07-01",
"sku": {
"name": "[parameters(\\'sku\\')]"
},
"kind": "Storage"
}
]'
)
# parameter values
build_template_parameters(name="mystorageacct", location="westus", sku="Standard_LRS")
build_template_parameters(
param='{
"name": "myname",
"properties": { "prop1": 42, "prop2": "hello" }
}'
)
param_json <- '{
"name": "myname",
"properties": { "prop1": 42, "prop2": "hello" }
}'
build_template_parameters(param=textConnection(param_json))
\dontrun{
# reading JSON definitions from a file
build_template_definition(
parameters=file("parameter_def.json"),
resources=file("resource_def.json")
build_template_parameters(name="myres_name", complex_type=file("myres_params.json"))
)
}
}

Просмотреть файл

@ -0,0 +1,14 @@
{
"name": "Allow-HTTP",
"properties": {
"protocol": "Tcp",
"access": "Allow",
"direction": "Inbound",
"destinationPortRange": "80",
"destinationAddressPrefix": "*",
"destinationApplicationSecurityGroups": [],
"sourcePortRange": "*",
"sourceAddressPrefix": "*",
"sourceApplicationSecurityGroups": []
}
}

Просмотреть файл

@ -0,0 +1,8 @@
{
"location": {
"type": "string"
},
"name": {
"type": "string"
}
}

Просмотреть файл

@ -0,0 +1,16 @@
[
{
"name": "[parameters('name')]",
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2018-07-01",
"location": "[parameters('location')]",
"properties": {
"supportsHttpsTrafficOnly": true
},
"dependsOn": [],
"sku": {
"name": "Standard_LRS"
},
"kind": "Storage"
}
]

Просмотреть файл

@ -9,7 +9,9 @@
"type": "string"
}
},
"variables": {},
"variables": {
},
"resources": [
{
"name": "[parameters('name')]",
@ -19,12 +21,17 @@
"properties": {
"supportsHttpsTrafficOnly": true
},
"dependsOn": [],
"dependsOn": [
],
"sku": {
"name": "Standard_LRS"
},
"kind": "Storage"
}
],
"outputs": {}
"outputs": {
}
}

Просмотреть файл

@ -0,0 +1,59 @@
context("Template builders")
test_that("Template definition builder works",
{
expect_silent(build_template_definition())
expect_silent(tpl1 <- build_template_definition(parameters=c(parm1="string", parm2="string")))
tpl2 <- build_template_definition(
parameters=list(parm1=list(type="string"), parm2=list(type="string")))
expect_identical(tpl1, tpl2)
expect_silent(tpl3 <- build_template_definition(
resource=list(
list(
name="resname", type="resprovider/type", properties=list(prop1=42, prop2="hello")
)
)
))
res_str <- '[
{
"name":"resname", "type":"resprovider/type", "properties":{ "prop1": 42, "prop2": "hello" }
}
]'
tpl4 <- build_template_definition(resource=res_str)
expect_identical(tpl3, tpl4)
tpl5 <- build_template_definition(resource=textConnection(res_str))
expect_identical(tpl3, tpl5)
expect_silent(tpl6 <- build_template_definition(
parameters=file("../resources/parameters.json"),
resources=file("../resources/resources.json")
))
expect_identical(unclass(tpl6), paste0(readLines("../resources/template.json"), collapse="\n"))
})
test_that("Template parameters builder works",
{
expect_identical(build_template_parameters(), "{}")
expect_silent(build_template_parameters(parm1="foo", parm2=list(bar="hello")))
expect_silent(par1 <- build_template_parameters(parm=file("../resources/parameter_values.json")))
par2 <- build_template_parameters(parm=readLines("../resources/parameter_values.json"))
expect_identical(par1, par2)
parm_str <- paste0(readLines("../resources/parameter_values.json"), collapse="\n")
par3 <- build_template_parameters(parm=textConnection(parm_str))
expect_identical(par1, par3)
par4 <- build_template_parameters(
parm=jsonlite::fromJSON("../resources/parameter_values.json", simplifyVector=FALSE))
expect_identical(par1, par4)
})

Просмотреть файл

@ -61,6 +61,17 @@ test_that("Template methods work",
tpl3$check()
expect_is(tpl3, "az_template")
expect_false(is_empty(rg$list_resources()))
# from template and parameter builder
tplname4 <- paste(sample(letters, 10, replace=TRUE), collapse="")
tpl_def <- build_template_definition(
parameters=file("../resources/parameters.json"),
resources=file("../resources/resources.json")
)
par_def <- build_template_parameters(location="australiaeast", name=tplname4)
tpl4 <- rg$deploy_template(tplname4, template=tpl_def, parameters=par_def, wait=TRUE)
tpl4$check()
expect_is(tpl4, "az_template")
})
rg$delete(confirm=FALSE)