зеркало из https://github.com/Azure/AzureRMR.git
374 строки
14 KiB
R
374 строки
14 KiB
R
#' Azure resource class
|
|
#'
|
|
#' Class representing a generic Azure resource.
|
|
#'
|
|
#' @docType class
|
|
#' @section Methods:
|
|
#' - `new(...)`: Initialize a new resource object. See 'Initialization' for more details.
|
|
#' - `delete(..., confirm=TRUE, wait=FALSE)`: Delete this resource, after a confirmation check. Optionally wait for the delete to finish.
|
|
#' - `update(...)`: Update this resource on the host.
|
|
#' - `sync_fields()`: Update the fields in this object with information from the host. Returns the `properties$provisioningState` field, so you can query this programmatically to check if a resource has finished provisioning. Not all resource types require explicit provisioning, in which case this method will return NULL.
|
|
#' - `set_api_version(api_version)`: Set the API version to use when interacting with the host. By default, use the latest API version available.
|
|
#' - `do_operation(...)` Carry out an operation. See 'Operations' for more details.
|
|
#'
|
|
#' @section Initialization:
|
|
#' There are multiple ways to initialize a new resource object. The `new()` method can retrieve an existing resource, deploy/create a new resource, or create an empty/null object (without communicating with the host), based on the arguments you supply.
|
|
#'
|
|
#' All of these initialization options have the following arguments in common.
|
|
#' 1. `token`: An OAuth 2.0 token, as generated by [get_azure_token].
|
|
#' 2. `subscription`: The subscription ID.
|
|
#' 3. `api_version`: Optionally, the API version to use when interacting with the host. By default, this is NULL in which case the latest API version will be used.
|
|
#' 4. A set of _identifying arguments_:
|
|
#' - `resource_group`: The resource group containing the resource.
|
|
#' - `id`: The full ID of the resource. This is a string of the form `/subscriptions/{uuid}/resourceGroups/{resource-group-name}/provider/{resource-provider-name}/{resource-path}/{resource-name}`.
|
|
#' - `provider`: The provider of the resource, eg `Microsoft.Compute`.
|
|
#' - `path`: The path to the resource, eg `virtualMachines`.
|
|
#' - `type`: The combination of provider and path, eg `Microsoft.Compute/virtualMachines`.
|
|
#' - `name`: The name of the resource instance, eg `myWindowsVM`.
|
|
#'
|
|
#' Providing `id` will fill in the values for all the other identifying arguments. Similarly, providing `type` will fill in the values for `provider` and `path`. Unless you provide `id`, you must also provide `name`.
|
|
#'
|
|
#' The default behaviour for `new()` is to retrieve an existing resource, which occurs if you supply only the arguments listed above. If you also supply an argument `deployed_properties=NULL`, this will create a null object. If you supply any other (named) arguments, `new()` will create a new object on the host, with the supplied arguments as parameters.
|
|
#'
|
|
#' Generally, the easiest way to initialize an object is via the `get_resource`, `create_resource` or `list_resources` methods of the [az_resource_group] class, which will handle all the gory details automatically.
|
|
#'
|
|
#' @section Operations:
|
|
#' The `do_operation()` method allows you to carry out arbitrary operations on the resource. It takes the following arguments:
|
|
#' - `op`: The operation in question, which will be appended to the URL path of the request.
|
|
#' - `options`: A named list giving the URL query parameters.
|
|
#' - `...`: Other named arguments passed to [call_azure_rm], and then to the appropriate call in httr. In particular, use `body` to supply the body of a PUT, POST or PATCH request.
|
|
#' - `http_verb`: The HTTP verb as a string, one of `GET`, `PUT`, `POST`, `DELETE`, `HEAD` or `PATCH`.
|
|
#'
|
|
#' Consult the Azure documentation for your resource to find out what operations are supported.
|
|
#'
|
|
#' @seealso
|
|
#' [az_resource_group], [call_azure_rm], [call_azure_url],
|
|
#' [Resources API reference](https://docs.microsoft.com/en-us/rest/api/resources/resources)
|
|
#'
|
|
#' @examples
|
|
#' \dontrun{
|
|
#'
|
|
#' # recommended way to retrieve a resource: via a resource group object
|
|
#' # storage account:
|
|
#' stor <- resgroup$get_resource(type="Microsoft.Storage/storageAccounts", name="mystorage")
|
|
#' # virtual machine:
|
|
#' vm <- resgroup$get_resource(type="Microsoft.Compute/virtualMachines", name="myvm")
|
|
#'
|
|
#' ## carry out operations on a resource
|
|
#'
|
|
#' # storage account: get access keys
|
|
#' stor$do_operation("listKeys", http_verb="POST")
|
|
#'
|
|
#' # virtual machine: run a script
|
|
#' vm$do_operation("runCommand",
|
|
#' body=list(
|
|
#' commandId="RunShellScript", # RunPowerShellScript for Windows
|
|
#' script=as.list("ifconfig > /tmp/ifconfig.out")
|
|
#' ),
|
|
#' encode="json",
|
|
#' http_verb="POST")
|
|
#'
|
|
#' ## retrieve properties
|
|
#'
|
|
#' # storage account: endpoint URIs
|
|
#' stor$properties$primaryEndpoints$file
|
|
#' stor$properties$primaryEndpoints$blob
|
|
#'
|
|
#' # virtual machine: hardware profile
|
|
#' vm$properties$hardwareProfile
|
|
#'
|
|
#' ## update a resource: resizing a VM
|
|
#' properties <- list(hardwareProfile=list(vmSize="Standard_DS3_v2"))
|
|
#' vm$do_operation(http_verb="PATCH",
|
|
#' body=list(properties=properties),
|
|
#' encode="json")
|
|
#'
|
|
#' # sync with Azure: useful to track resource creation/update status
|
|
#' vm$sync_fields()
|
|
#'
|
|
#' # delete a resource
|
|
#' stor$delete()
|
|
#'
|
|
#' }
|
|
#' @format An R6 object of class `az_resource`.
|
|
#' @export
|
|
az_resource <- R6::R6Class("az_resource",
|
|
|
|
public=list(
|
|
subscription=NULL,
|
|
resource_group=NULL,
|
|
type=NULL,
|
|
name=NULL,
|
|
id=NULL,
|
|
identity=NULL,
|
|
kind=NULL,
|
|
location=NULL,
|
|
managed_by=NULL,
|
|
plan=NULL,
|
|
properties=NULL,
|
|
sku=NULL,
|
|
tags=NULL,
|
|
token=NULL,
|
|
etag=NULL,
|
|
|
|
# constructor overloads:
|
|
# 1. deploy resource: resgroup, {provider, path}|type, name, ...
|
|
# 2. deploy resource by id: id, ...
|
|
# 3. get from passed-in data: deployed_properties
|
|
# 4. get from host: resgroup, {provider, path}|type, name
|
|
# 5. get from host by id: id
|
|
initialize=function(token, subscription, resource_group, provider, path, type, name, id, ...,
|
|
deployed_properties=list(), api_version=NULL, wait=FALSE)
|
|
{
|
|
self$token <- token
|
|
self$subscription <- subscription
|
|
|
|
private$init_id_fields(resource_group, provider, path, type, name, id, deployed_properties)
|
|
|
|
# by default this is unset at initialisation, for efficiency
|
|
private$api_version <- api_version
|
|
|
|
parms <- if(!is_empty(list(...)))
|
|
private$init_and_deploy(..., wait=wait)
|
|
else if(!is_empty(deployed_properties))
|
|
private$init_from_parms(deployed_properties)
|
|
else private$init_from_host()
|
|
|
|
self$identity <- parms$identity
|
|
self$kind <- parms$kind
|
|
self$location <- parms$location
|
|
self$managed_by <- parms$managedBy
|
|
self$plan <- parms$plan
|
|
self$properties <- parms$properties
|
|
self$sku <- parms$sku
|
|
self$tags <- parms$tags
|
|
self$etag <- parms$etag
|
|
|
|
NULL
|
|
},
|
|
|
|
# API versions vary across different providers; find the latest for this resource
|
|
set_api_version=function(api_version=NULL)
|
|
{
|
|
if(!is_empty(api_version))
|
|
{
|
|
private$api_version <- api_version
|
|
return()
|
|
}
|
|
|
|
slash <- regexpr("/", self$type)
|
|
provider <- substr(self$type, 1, slash - 1)
|
|
path <- substr(self$type, slash + 1, nchar(self$type))
|
|
|
|
op <- construct_path("providers", provider)
|
|
apis <- named_list(call_azure_rm(self$token, self$subscription, op)$resourceTypes, "resourceType")
|
|
|
|
names(apis) <- tolower(names(apis))
|
|
private$api_version <- apis[[tolower(path)]]$apiVersions[[1]]
|
|
if(is_empty(private$api_version))
|
|
stop("Unable to retrieve API version for resource '", self$type, "'.", call.=FALSE)
|
|
|
|
invisible(private$api_version)
|
|
},
|
|
|
|
sync_fields=function()
|
|
{
|
|
self$initialize(self$token, self$subscription, id=self$id)
|
|
self$properties$provisioningState
|
|
},
|
|
|
|
delete=function(..., options=list(), confirm=TRUE, wait=FALSE)
|
|
{
|
|
if(confirm && interactive())
|
|
{
|
|
yn <- readline(paste0("Do you really want to delete resource '", self$type, "/", self$name, "'? (y/N) "))
|
|
if(tolower(substr(yn, 1, 1)) != "y")
|
|
return(invisible(NULL))
|
|
}
|
|
|
|
message("Deleting resource '", construct_path(self$type, self$name), "'")
|
|
private$res_op(..., options=options, http_verb="DELETE")
|
|
|
|
if(wait)
|
|
{
|
|
for(i in 1:1000)
|
|
{
|
|
status <- httr::status_code(private$res_op(http_status_handler="pass"))
|
|
if(status >= 300)
|
|
break
|
|
Sys.sleep(5)
|
|
}
|
|
if(status < 300)
|
|
warning("Attempt to delete resource did not succeed", call.=FALSE)
|
|
}
|
|
|
|
invisible(NULL)
|
|
},
|
|
|
|
do_operation=function(..., options=list(), http_verb="GET")
|
|
{
|
|
private$res_op(..., options=options, http_verb=http_verb)
|
|
},
|
|
|
|
update=function(..., options=list())
|
|
{
|
|
parms <- list(...)
|
|
private$validate_update_parms(names(parms))
|
|
private$res_op(body=parms, options=options, encode="json", http_verb="PATCH")
|
|
self$sync_fields()
|
|
},
|
|
|
|
set_tags=function(..., keep_existing=TRUE)
|
|
{
|
|
tags <- match.call(expand.dots=FALSE)$...
|
|
unvalued <- names(tags) == ""
|
|
|
|
values <- lapply(seq_along(unvalued), function(i)
|
|
{
|
|
if(unvalued[i]) "" else as.character(eval.parent(tags[[i]]))
|
|
})
|
|
names(values) <- ifelse(unvalued, as.character(tags), names(tags))
|
|
|
|
if(keep_existing)
|
|
values <- modifyList(self$tags, values)
|
|
|
|
if(is.null(values))
|
|
values <- list()
|
|
|
|
self$update(tags=values)
|
|
},
|
|
|
|
get_tags=function()
|
|
{
|
|
self$tags
|
|
},
|
|
|
|
print=function(...)
|
|
{
|
|
# generate label from id, since type and name are not guaranteed to be fixed for sub-resources
|
|
cat("<Azure resource ", sub("^.+providers/(.+$)", "\\1", self$id), ">\n", sep="")
|
|
cat(format_public_fields(self, exclude=c("subscription", "resource_group", "type", "name")))
|
|
cat(format_public_methods(self))
|
|
invisible(NULL)
|
|
}
|
|
),
|
|
|
|
private=list(
|
|
api_version=NULL,
|
|
|
|
# initialise identifier fields from multiple ways of constructing object
|
|
init_id_fields=function(resource_group, provider, path, type, name, id, parms=list())
|
|
{
|
|
# if these are supplied, use to fill in everything else
|
|
if(!is_empty(parms$id) && !is_empty(parms$type) && !is_empty(parms$name))
|
|
{
|
|
resource_group <- sub("^.+resourceGroups/([^/]+)/.*$", "\\1", parms$id, ignore.case=TRUE)
|
|
type <- parms$type
|
|
name <- parms$name
|
|
id <- parms$id
|
|
}
|
|
else if(!missing(id))
|
|
{
|
|
resource_group <- sub("^.+resourceGroups/([^/]+)/.*$", "\\1", id, ignore.case=TRUE)
|
|
id2 <- sub("^.+providers/", "", id)
|
|
type_delim <- attr(regexpr("^[^/]+/[^/]+/", id2), "match.length")
|
|
type <- substr(id2, 1, type_delim - 1)
|
|
name <- substr(id2, type_delim + 1, nchar(id2))
|
|
}
|
|
else
|
|
{
|
|
if(missing(type))
|
|
type <- construct_path(provider, path)
|
|
id <- construct_path("/subscriptions", self$subscription, "resourceGroups", resource_group,
|
|
"providers", type, name)
|
|
}
|
|
self$resource_group <- resource_group
|
|
self$type <- type
|
|
self$name <- name
|
|
self$id <- id
|
|
},
|
|
|
|
init_from_parms=function(parms)
|
|
{
|
|
# allow list(NULL) as special case for creating an empty object
|
|
if(!identical(parms, list(NULL)))
|
|
private$validate_response_parms(parms)
|
|
parms
|
|
},
|
|
|
|
init_from_host=function()
|
|
{
|
|
private$res_op()
|
|
},
|
|
|
|
init_and_deploy=function(..., wait)
|
|
{
|
|
properties <- list(...)
|
|
|
|
# check if we were passed a json object
|
|
if(length(properties) == 1 && is.character(properties[[1]]) && jsonlite::validate(properties[[1]]))
|
|
properties <- jsonlite::fromJSON(properties[[1]], simplifyVector=FALSE)
|
|
|
|
private$validate_deploy_parms(properties)
|
|
private$res_op(body=properties, encode="json", http_verb="PUT")
|
|
|
|
# do we wait until resource has finished provisioning?
|
|
if(wait)
|
|
{
|
|
message("Waiting for provisioning to complete")
|
|
for(i in 1:1000) # some resources can take a long time to provision (AKS, Kusto)
|
|
{
|
|
Sys.sleep(5)
|
|
res <- private$res_op()
|
|
status <- res$properties$provisioningState
|
|
if(status %in% c("Succeeded", "Error", "Failed"))
|
|
break
|
|
message(".", appendLF=FALSE)
|
|
}
|
|
if(status == "Succeeded")
|
|
message("\nDeployment successful")
|
|
else stop("\nUnable to create resource", call.=FALSE)
|
|
}
|
|
else
|
|
{
|
|
# allow time for provisioning setup, then get properties
|
|
Sys.sleep(2)
|
|
res <- private$res_op()
|
|
}
|
|
res
|
|
},
|
|
|
|
validate_deploy_parms=function(parms)
|
|
{
|
|
required_names <- character(0)
|
|
optional_names <-
|
|
c("identity", "kind", "location", "managedBy", "plan", "properties", "sku", "tags", "etag")
|
|
validate_object_names(names(parms), required_names, optional_names)
|
|
},
|
|
|
|
validate_response_parms=function(parms)
|
|
{
|
|
required_names <- c("id", "name", "type", "location")
|
|
optional_names <- c("identity", "kind", "managedBy", "plan", "properties", "sku", "tags", "etag")
|
|
validate_object_names(names(parms), required_names, optional_names)
|
|
},
|
|
|
|
validate_update_parms=function(parms)
|
|
{
|
|
required_names <- character(0)
|
|
optional_names <-
|
|
c("identity", "kind", "location", "managedBy", "plan", "properties", "sku", "tags", "etag")
|
|
validate_object_names(names(parms), required_names, optional_names)
|
|
},
|
|
|
|
res_op=function(op="", ..., api_version=private$api_version)
|
|
{
|
|
# make sure we have an API to call
|
|
if(is.null(private$api_version))
|
|
self$set_api_version()
|
|
|
|
op <- construct_path("resourcegroups", self$resource_group, "providers", self$type, self$name, op)
|
|
call_azure_rm(self$token, self$subscription, op, ..., api_version=api_version)
|
|
}
|
|
))
|