AzureVM/R/az_vm_template.R

227 строки
8.7 KiB
R

#' Virtual machine template class
#'
#' Class representing a virtual machine deployment template. This class keeps track of all resources that are created as part of deploying a VM, and exposes methods for managing them.
#'
#' @docType class
#' @section Fields:
#' The following fields are exposed, in addition to those provided by the [AzureRMR::az_template] class.
#' - `dns_name`: The DNS name for the VM. Will be NULL if the VM is not publicly visible, or doesn't have a domain name assigned to its public IP address.
#' - `identity`: The managed identity details for the VM. Will be NULL if the VM doesn't have an identity assigned.
#' @section Methods:
#' The following methods are available, in addition to those provided by the [AzureRMR::az_template] class.
#' - `start(wait=TRUE)`: Start the VM. By default, wait until the startup process is complete.
#' - `stop(deallocate=TRUE, wait=FALSE)`: Stop the VM. By default, deallocate it as well.
#' - `restart(wait=TRUE)`: Restart the VM.
#' - `run_deployed_command(command, parameters, script)`: Run a PowerShell command on the VM.
#' - `run_script(script, parameters)`: Run a script on the VM. For a Linux VM, this will be a shell script; for a Windows VM, a PowerShell script. Pass the script as a character vector.
#' - `sync_vm_status()`: Check the status of the VM.
#' - `resize(size, deallocate=FALSE, wait=FALSE)`: Resize the VM. Optionally stop and deallocate it first (may sometimes be necessary).
#' - `redeploy()`: Redeploy the VM.
#' - `reimage()`: Reimage the VM.
#' - `get_public_ip_address(nic=1, config=1)`: Get the public IP address of the VM. Returns NA if the VM is stopped, or is not publicly accessible.
#' - `get_private_ip_address(nic=1, config=1)`: Get the private IP address of the VM.
#' - `get_public_ip_resource(nic=1, config=1)`: Get the Azure resource for the VM's public IP address.
#' - `get_nic(nic=1)`: Get the VM's network interface resource.
#' - `get_vnet(nic=1, config=1)`: Get the VM's virtual network resource.
#' - `get_nsg(nic=1, config=1)`: Get the VM's network security group resource. Note that an NSG can be attached to either the VM's network interface or to its virtual network subnet; if there is an NSG attached to both, this method returns a list containing the two NSG resource objects.
#' - `get_disk(disk="os")`: Get a managed disk resource attached to the VM. The `disk` argument can be "os" for the OS disk, or a number indicating the LUN of a data disk. AzureVM only supports managed disks.
#' - `add_extension(publisher, type, version, settings=list(), protected_settings=list(), key_vault_settings=list())`: Add an extension to the VM.
#' - `do_vm_operation(...)`: Carries out an arbitrary operation on the VM resource. See the `do_operation` method of the [AzureRMR::az_resource] class for more details.
#'
#' Many of these methods are actually provided by the [az_vm_resource] class, and propagated to the template as active bindings.
#'
#' @details
#' A single virtual machine in Azure is actually a collection of resources, including any and all of the following.
#' - Network interface (Azure resource type `Microsoft.Network/networkInterfaces`)
#' - Network security group (Azure resource type `Microsoft.Network/networkSecurityGroups`)
#' - Virtual network (Azure resource type `Microsoft.Network/virtualNetworks`)
#' - Public IP address (Azure resource type `Microsoft.Network/publicIPAddresses`)
#' - The VM itself (Azure resource type `Microsoft.Compute/virtualMachines`)
#'
#' By wrapping the deployment template used to create these resources, the `az_vm_template` class allows managing them all as a single entity.
#'
#' @seealso
#' [AzureRMR::az_template], [create_vm], [get_vm], [delete_vm]
#'
#' [VM API reference](https://docs.microsoft.com/en-us/rest/api/compute/virtualmachines)
#'
#' @examples
#' \dontrun{
#'
#' sub <- AzureRMR::get_azure_login()$
#' get_subscription("subscription_id")
#'
#' vm <- sub$get_vm("myvm")
#'
#' vm$identity
#'
#' vm$start()
#' vm$get_private_ip_address()
#' vm$get_public_ip_address()
#'
#' vm$run_script("echo hello world! > /tmp/hello.txt")
#'
#' vm$stop()
#' vm$get_private_ip_address()
#' vm$get_public_ip_address() # NA, assuming VM has a dynamic IP address
#'
#' vm$resize("Standard_DS13_v2")
#' vm$sync_vm_status()
#'
#' }
#' @format An R6 object of class `az_vm_template`, inheriting from `AzureRMR::az_template`.
#' @export
az_vm_template <- R6::R6Class("az_vm_template", inherit=az_template,
public=list(
dns_name=NULL,
initialize=function(token, subscription, resource_group, name, ..., wait=TRUE)
{
super$initialize(token, subscription, resource_group, name, ..., wait=wait)
if(wait)
{
private$vm <- az_vm_resource$new(self$token, self$subscription, id=self$properties$outputs$vmResource$value)
# get the hostname/IP address for the VM
outputs <- unlist(self$properties$outputResources)
ip_id <- grep("publicIPAddresses/.+$", outputs, ignore.case=TRUE, value=TRUE)
if(!is_empty(ip_id))
{
ip <- az_resource$new(self$token, self$subscription, id=ip_id)
self$dns_name <- ip$properties$dnsSettings$fqdn
}
}
else message("Deployment started. Call the sync_vm_status() method to track the status of the deployment.")
},
delete=function(confirm=TRUE, free_resources=TRUE)
{
# must reorder template output resources so that freeing resources will work
private$reorder_for_delete()
super$delete(confirm=confirm, free_resources=free_resources)
},
print=function(...)
{
cat("<Azure virtual machine ", self$name, ">\n", sep="")
osProf <- names(private$vm$properties$osProfile)
os <- if(any(grepl("linux", osProf))) "Linux" else if(any(grepl("windows", osProf))) "Windows" else "<unknown>"
exclusive <- self$properties$mode == "Complete"
status <- if(is_empty(private$vm$status))
"<unknown>"
else paste0(names(private$vm$status), "=", private$vm$status, collapse=", ")
cat(" Operating system:", os, "\n")
cat(" Exclusive resource group:", exclusive, "\n")
cat(" Domain name:", self$dns_name, "\n")
cat(" Status:", status, "\n")
cat("---\n")
exclude <- c("subscription", "resource_group", "name", "dns_name")
cat(AzureRMR::format_public_fields(self, exclude=exclude))
cat(AzureRMR::format_public_methods(self))
invisible(NULL)
}
),
# propagate resource methods up to template
active=list(
identity=function()
private$vm$identity,
sync_vm_status=function()
private$vm$sync_vm_status,
start=function()
private$vm$start,
stop=function()
private$vm$stop,
restart=function()
private$vm$restart,
add_extension=function()
private$vm$add_extension,
resize=function()
private$vm$resize,
run_deployed_command=function()
private$vm$run_deployed_command,
run_script=function()
private$vm$run_script,
get_public_ip_address=function()
private$vm$get_public_ip_address,
get_private_ip_address=function()
private$vm$get_private_ip_address,
get_public_ip_resource=function()
private$vm$get_public_ip_resource,
get_nic=function()
private$vm$get_nic,
get_vnet=function()
private$vm$get_vnet,
get_nsg=function()
private$vm$get_nsg,
get_disk=function()
private$vm$get_disk,
redeploy=function()
private$vm$redeploy,
reimage=function()
private$vm$reimage,
do_vm_operation=function()
private$vm$do_operation
),
private=list(
vm=NULL,
reorder_for_delete=function()
{
is_type <- function(id, type)
{
grepl(type, id, fixed=TRUE)
}
# insert managed disks into deletion queue
stor <- private$vm$properties$storageProfile
managed_disks <- c(
stor$osDisk$managedDisk$id,
lapply(stor$dataDisks, function(x) x$managedDisk$id)
)
outs <- unique(c(unlist(self$properties$outputResources), unlist(managed_disks)))
new_order <- sapply(outs, function(id)
{
if(is_type(id, "Microsoft.Compute/virtualMachines")) 1
else if(is_type(id, "Microsoft.Compute/disks")) 2
else if(is_type(id, "Microsoft.Network/networkInterfaces")) 3
else if(is_type(id, "Microsoft.Network/virtualNetworks")) 4
else if(is_type(id, "Microsoft.Network/publicIPAddresses")) 5
else if(is_type(id, "Microsoft.Network/networkSecurityGroups")) 6
else 0 # delete all other resources first
})
outs <- outs[order(new_order)]
self$properties$outputResources <- lapply(outs, function(x) list(id=x))
}
))