diff --git a/.Rbuildignore b/.Rbuildignore index 10b6f7d..323f209 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -8,3 +8,5 @@ CONTRIBUTING.md drat.sh .travis.yml ^LICENSE\.md$ +azure-pipelines.yml +^tpl$ diff --git a/DESCRIPTION b/DESCRIPTION index fb11efe..e50ad62 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,13 +1,12 @@ Package: AzureVM Title: Virtual Machines in 'Azure' -Version: 1.0.1 +Version: 2.0.0 Authors@R: c( person("Hong", "Ooi", , "hongooi@microsoft.com", role = c("aut", "cre")), - person("Data Science Virtual Machine team", role="ctb", comment="DSVM template provider"), person("Microsoft", role="cph") ) -Description: Functionality for working with virtual machines (VMs) in Microsoft's 'Azure' cloud: . Includes facilities to create, startup, shutdown, and cleanly delete VMs and VM clusters. With a running VM, execute scripts and install optional extensions. A selection of VM templates based on the 'Data Science Virtual Machine' (DSVM) is supplied; this allows fast and easy provisioning of a VM preinstalled with several software packages useful for data science. Alternatively, users can provide VM templates of their own. -URL: https://github.com/Azure/AzureVM +Description: Functionality for working with virtual machines (VMs) in Microsoft's 'Azure' cloud: . Includes facilities to deploy, startup, shutdown, and cleanly delete VMs and VM clusters. Deployment configurations can be highly customised, and can make use of existing resources as well as creating new ones. A selection of predefined configurations is provided to allow easy deployment of commonly used Linux and Windows images, including Data Science Virtual Machines. With a running VM, execute scripts and install optional extensions. Part of the 'AzureR' family of packages. +URL: https://github.com/Azure/AzureVM https://github.com/Azure/AzureR BugReports: https://github.com/Azure/AzureVM/issues License: MIT + file LICENSE VignetteBuilder: knitr @@ -15,9 +14,11 @@ Depends: R (>= 3.3), Imports: R6, - AzureRMR + AzureRMR (>= 2.1.2), + jsonlite Suggests: knitr, - testthat + testthat, + AzureKeyVault Roxygen: list(markdown=TRUE) -RoxygenNote: 6.1.0.9000 +RoxygenNote: 6.1.1 diff --git a/NAMESPACE b/NAMESPACE index 4c61ee5..4a4b1c0 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,6 +1,79 @@ # Generated by roxygen2: do not edit by hand +S3method(build_template_definition,vm_config) +S3method(build_template_definition,vmss_config) +S3method(build_template_parameters,vm_config) +S3method(build_template_parameters,vmss_config) +export(autoscaler_config) +export(autoscaler_profile) export(az_vm_resource) export(az_vm_template) +export(az_vmss_resource) +export(az_vmss_template) +export(build_template_definition) +export(build_template_parameters) +export(datadisk_config) +export(debian_9_backports) +export(debian_9_backports_ss) +export(image_config) +export(ip_config) +export(is_vm) +export(is_vm_resource) +export(is_vm_scaleset) +export(is_vm_scaleset_resource) +export(is_vm_scaleset_template) export(is_vm_template) +export(lb_config) +export(lb_probe) +export(lb_probe_http) +export(lb_probe_https) +export(lb_probe_jupyter) +export(lb_probe_mssql) +export(lb_probe_mssql_browser) +export(lb_probe_rdp) +export(lb_probe_rstudio) +export(lb_probe_ssh) +export(lb_rule) +export(lb_rule_http) +export(lb_rule_https) +export(lb_rule_jupyter) +export(lb_rule_mssql) +export(lb_rule_mssql_browser) +export(lb_rule_rdp) +export(lb_rule_rstudio) +export(lb_rule_ssh) +export(nic_config) +export(nic_ip_config) +export(nsg_config) +export(nsg_rule) +export(nsg_rule_allow_http) +export(nsg_rule_allow_https) +export(nsg_rule_allow_jupyter) +export(nsg_rule_allow_mssql) +export(nsg_rule_allow_mssql_browser) +export(nsg_rule_allow_rdp) +export(nsg_rule_allow_rstudio) +export(nsg_rule_allow_ssh) +export(rhel_7.6) +export(rhel_7.6_ss) +export(rhel_8) +export(rhel_8_ss) +export(scaleset_options) +export(subnet_config) +export(ubuntu_16.04) +export(ubuntu_16.04_ss) +export(ubuntu_18.04) +export(ubuntu_18.04_ss) +export(ubuntu_dsvm) +export(ubuntu_dsvm_ss) +export(user_config) +export(vm_config) +export(vmss_config) +export(vnet_config) +export(windows_2016) +export(windows_2016_ss) +export(windows_2019) +export(windows_2019_ss) +export(windows_dsvm) +export(windows_dsvm_ss) import(AzureRMR) diff --git a/NEWS.md b/NEWS.md index 45a2092..e8bccf5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,12 @@ +# AzureVM 2.0.0 + +* Complete rewrite of package, to be less DSVM-centric and more flexible: + * Separate out deployment of VMs and VM clusters; the latter are implemented as single scaleset resources, rather than simple arrays of individual VMs. + * `vm_config` and `vmss_config` functions to fine-tune the deployment options, including specifying the base VM image; networking details like security rules, load balancers and autoscaling; datadisks to attach; use of low-priority VMs for scalesets; etc. + * Several predefined configurations supplied to allow quick deployment of commonly used images (Ubuntu, Windows Server, RHEL, Debian). + * Allow referring to existing resources in a deployment, by supplying `AzureRMR::az_resource` objects as arguments. + * See the README and/or the vignette for more information. + # AzureVM 1.0.1 * Allow resource group and subscription accessor methods to work without AzureVM on the search path. diff --git a/R/AzureVM.R b/R/AzureVM.R index 6d58b40..533797c 100644 --- a/R/AzureVM.R +++ b/R/AzureVM.R @@ -1,4 +1,11 @@ #' @import AzureRMR NULL +#' @export +AzureRMR::build_template_definition + +#' @export +AzureRMR::build_template_parameters + globalVariables("self", "AzureVM") + diff --git a/R/add_methods.R b/R/add_methods.R index 934363f..0fac3a2 100644 --- a/R/add_methods.R +++ b/R/add_methods.R @@ -25,8 +25,7 @@ #' @examples #' \dontrun{ #' -#' sub <- AzureRMR::az_rm$ -#' new(tenant="myaadtenant.onmicrosoft.com", app="app_id", password="password")$ +#' sub <- AzureRMR::get_azure_login$ #' get_subscription("subscription_id") #' #' sub$list_vm_sizes("australiaeast") @@ -55,156 +54,204 @@ NULL #' get_vm(name) #' #' ## R6 method for class 'az_subscription' -#' get_vm_cluster(name, resource_group = name) +#' get_vm_scaleset(name, resource_group = name) #' #' ## R6 method for class 'az_resource_group' -#' get_vm_cluster(name) +#' get_vm_scaleset(name) #' -#' ## R6 method for class 'az_resource_group' -#' ## R6 method for class 'az_subscription' -#' list_vms() +#' ## R6 method for class 'az_resource_group') +#' get_vm_resource(name) +#' get_vm_scaleset_resource(name) #' ``` #' @section Arguments: -#' - `name`: The name of the VM or cluster. -#' - `resource_group`: For the `az_subscription` method, the resource group in which `get_vm()` will look for the VM. Defaults to the VM name. -#' -#' @section Details: -#' Despite the names, `get_vm` and `get_vm_cluster` can both be used to retrieve individual VMs and clusters. The main difference is in their behaviour if a deployment template is not found. In the case of `get_vm`, it also searches for a raw VM resource of the given name, whereas `get_vm_cluster` will throw an error immediately. +#' - `name`: The name of the VM or scaleset. +#' - `resource_group`: For the `az_subscription` methods, the resource group in which `get_vm()` and `get_vm_scaleset()` will look for the VM or scaleset. Defaults to the VM name. #' #' @section Value: -#' For `get_vm()`, an object representing the VM, either of class `az_vm_template` or `az_vm_resource`. +#' For `get_vm()`, an object representing the VM deployment. This will include other resources besides the VM itself, such as the network interface, virtual network, etc. #' -#' For `list_vms()`, a list of such objects. +#' For `get_vm_scaleset()`, an object representing the scaleset deployment. Similarly to `get_vm()`, this includes other resources besides the scaleset. #' -#' For `get_vm_cluster()`, an object representing the cluster. +#' For `get_vm_resource()` and `get_vm_scaleset_resource()`, the VM or scaleset resource itself. #' #' @seealso -#' [az_vm_template], [az_vm_resource], +#' [az_vm_template], [az_vm_resource], [az_vmss_template], [az_vmss_resource] for the methods available for working with VMs and VM scalesets. +#' #' [AzureRMR::az_subscription], [AzureRMR::az_resource_group] #' #' @examples #' \dontrun{ -#' -#' sub <- AzureRMR::az_rm$ -#' new(tenant="myaadtenant.onmicrosoft.com", app="app_id", password="password")$ +#' +#' sub <- AzureRMR::get_azure_login()$ #' get_subscription("subscription_id") #' -#' sub$list_vms() -#' sub$get_vm("myVirtualMachine") +#' sub$get_vm("myvirtualmachine") +#' sub$get_vm_scaleset("myscaleset") #' #' rg <- sub$get_resource_group("rgname") -#' rg$get_vm("myOtherVirtualMachine") -#' +#' rg$get_vm("myothervirtualmachine") +#' rg$get_vm_scaleset("myotherscaleset") +#' #' } #' @rdname get_vm -#' @aliases get_vm get_vm_cluster list_vms +#' @aliases get_vm get_vm_scaleset get_vm_resource get_vm_scaleset_resource #' @name get_vm NULL -#' Create a new virtual machine or cluster of virtual machines +#' Create a new virtual machine or scaleset of virtual machines #' #' Method for the [AzureRMR::az_subscription] and [AzureRMR::az_resource_group] classes. #' #' @section Usage: #' ``` #' ## R6 method for class 'az_resource_group' -#' create_vm(name, os = c("Windows", "Ubuntu"), size = "Standard_DS3_v2", -#' username, passkey, userauth_type = c("password", "key"), -#' ext_file_uris = NULL, inst_command = NULL, -#' template, parameters, ..., wait = TRUE) +#' create_vm(name, login_user, size = "Standard_DS3_v2", config = "ubuntu_dsvm", +#' managed = TRUE, datadisks = numeric(0), ..., +#' template, parameters, mode = "Incremental", wait = TRUE) #' #' ## R6 method for class 'az_subscription' -#' create_vm(name, location, os = c("Windows", "Ubuntu"), size = "Standard_DS3_v2", -#' username, passkey, userauth_type = c("password", "key"), -#' ext_file_uris = NULL, inst_command = NULL, -#' template, parameters, ..., wait = TRUE) +#' create_vm(name, ..., resource_group = name, location) #' #' ## R6 method for class 'az_resource_group' -#' create_vm_cluster(name, os = c("Windows", "Ubuntu"), size = "Standard_DS3_v2", -#' username, passkey, userauth_type = c("password", "key"), -#' ext_file_uris = NULL, inst_command = NULL, clust_size, -#' template, parameters, ..., wait = TRUE) +#' create_vm_scaleset(name, login_user, instances, size = "Standard_DS1_v2", +#' config = "ubuntu_dsvm_ss", ..., +#' template, parameters, mode = "Incremental", wait = TRUE) #' #' ## R6 method for class 'az_subscription' -#' create_vm_cluster(name, location, os = c("Windows", "Ubuntu"), size = "Standard_DS3_v2", -#' username, passkey, userauth_type = c("password", "key"), -#' ext_file_uris = NULL, inst_command = NULL, clust_size, -#' template, parameters, ..., wait = TRUE) - +#' create_vm_scaleset(name, ..., resource_group = name, location) #' ``` #' @section Arguments: -#' - `name`: The name of the VM or cluster. -#' - `location`: For the subscription class methods, the location for the VM. Use the `list_locations()` method of the `AzureRMR::az_subscription` class to see what locations are available. -#' - `os`: The operating system for the VM. -#' - `size`: The VM size. Use the `list_vm_sizes()` method of the `AzureRMR::az_subscription` class to see what sizes are available. -#' - `username`: The login username for the VM. -#' - `passkey`: The login password or public key. -#' - `userauth_type`: The type of login authentication to use. Only has an effect for Linux-based VMs; Windows VMs will always use `"password"`. -#' - `ext_file_uris`: Optional link to download extension packages. -#' - `inst_command`: If `ext_file_uris` is supplied, the install script to run. Defaults to `install.sh` for an Ubuntu VM, or `install.ps1` for a Windows VM. -#' - `clust_size`: For a cluster, the number of nodes to create. -#' - `template`: Optional: the VM template to deploy. By default, this is determined by the values of the other arguments; see 'Details' below. -#' - `parameters`: Optional: other parameters to pass to the deployment. +#' - `name`: The name of the VM or scaleset. +#' - `location`: For the subscription methods, the location for the VM or scaleset. Use the `list_locations()` method of the `AzureRMR::az_subscription` class to see what locations are available. +#' - `resource_group`: For the subscription methods, the resource group in which to place the VM or scaleset. Defaults to a new resource group with the same name as the VM. +#' - `login_user`: The details for the admin login account. An object of class `user_config`, obtained by a call to the `user_config` function. +#' - `size`: The VM (instance) size. Use the [list_vm_sizes] method to see what sizes are available. +#' - `config`: The VM or scaleset configuration. See 'Details' below for how to specify this. The default is to use an Ubuntu Data Science Virtual Machine. +#' - `managed`: For `create_vm`, whether the VM should have a managed identity attached. +#' - `datadisks`: For `create_vm`, any data disks to attach to the VM. See 'Details' below. +#' - `instances`: For `create_vm_scaleset`, the initial number of instances in the scaleset. +#' - `...` For the subscription methods, any of the other arguments listed here, which will be passed to the resource group method. For the resource group method, additional arguments to pass to the VM/scaleset configuration functions [vm_config] and [vmss_config]. See the examples below. +#' - `template,parameters`: The template definition and parameters to deploy. By default, these are constructed from the values of the other arguments, but you can supply your own template and/or parameters as well. #' - `wait`: Whether to wait until the deployment is complete. -#' - `...`: Other arguments to lower-level methods. +#' - `mode`: The template deployment mode. If "Complete", any existing resources in the resource group will be deleted. #' #' @section Details: -#' This method deploys a template to create a new virtual machine or cluster of VMs. Currently, seven templates are supplied with this package, based on the Azure Data Science Virtual Machine: -#' - Ubuntu DSVM -#' - Ubuntu DSVM using public key authentication -#' - Ubuntu DSVM with extensions -#' - Ubuntu DSVM cluster -#' - Ubuntu DSVM cluster with extensions -#' - Windows Server 2016 DSVM -#' - Windows Server 2016 DSVM cluster with extensions +#' These methods deploy a template to create a new virtual machine or scaleset. #' -#' An individual virtual machine is treated as a cluster containing only a single node. +#' The `config` argument can be specified in the following ways: +#' - As the name of a supplied VM or scaleset configuration, like "ubuntu_dsvm" or "ubuntu_dsvm_ss". AzureVM comes with a number of supplied configurations to deploy commonly used images, which can be seen at [vm_config] and [vmss_config]. Any arguments in `...` will be passed to the configuration, allowing you to customise the deployment. +#' - As a call to the `vm_config` or `vmss_config` functions, to deploy a custom VM image. +#' - As an object of class `vm_config` or `vmss_config`. #' -#' You can also supply your own VM template for deployment, via the `template` argument. See [AzureRMR::az_template] for information how to supply templates. Note that if you do this, you may also have to supply a `parameters` argument, as the standard parameters for this method are customised for the DSVM. +#' The data disks for the VM can be specified as either a vector of numeric disk sizes in GB, or as a list of `datadisk_config` objects, created via calls to the `datadisk_config` function. Currently, AzureVM only supports creating data disks at deployment time for single VMs, not scalesets. #' -#' For the `AzureRMR::az_subscription` method, this will by default create the VM in _exclusive_ mode, meaning a new resource group is created solely to hold the VM. This simplifies managing the VM considerably, in particular deleting the resource group will also automatically delete all the VM's resources. +#' You can also supply your own template definition and parameters for deployment, via the `template` and `parameters` arguments. See [AzureRMR::az_template] for information how to create templates. +#' +#' The `AzureRMR::az_subscription` methods will by default create the VM in _exclusive_ mode, meaning a new resource group is created solely to hold the VM or scaleset. This simplifies managing the VM considerably; in particular deleting the resource group will also automatically delete all the deployed resources. #' #' @section Value: -#' An object of class `az_vm_template` representing the created VM. +#' For `create_vm`, an object of class `az_vm_template` representing the created VM. For `create_vm_scaleset`, an object of class `az_vmss_template` representing the scaleset. #' #' @seealso -#' [az_vm_template], +#' [az_vm_template], [az_vmss_template] +#' +#' [vm_config], [vmss_config], [user_config], [datadisk_config] +#' #' [AzureRMR::az_subscription], [AzureRMR::az_resource_group], #' [Data Science Virtual Machine](https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/) #' #' @examples #' \dontrun{ -#' -#' sub <- AzureRMR::az_rm$ -#' new(tenant="myaadtenant.onmicrosoft.com", app="app_id", password="password")$ +#' +#' sub <- AzureRMR::get_azure_login()$ #' get_subscription("subscription_id") #' -#' # default Windows Server DSVM: make sure to use a strong password! -#' sub$create_vm("myWindowsDSVM", -#' location="australiaeast", -#' username="ds", -#' passkey="Password123!") +#' # default Ubuntu 18.04 VM: +#' # SSH key login, Standard_DS3_v2, publicly accessible via SSH +#' sub$create_vm("myubuntuvm", user_config("myname", "~/.ssh/id_rsa.pub"), +#' location="australiaeast") #' -#' # upsized Linux (Ubuntu) DSVM -#' sub$create_vm("myLinuxDSVM", -#' location="australiaeast", -#' os="Linux", -#' username="ds", -#' passkey=readLines("~/id_rsa.pub"), -#' size="Standard_DS13_v2") +#' # Windows Server 2019, with a 500GB datadisk attached, not publicly accessible +#' sub$create_vm("mywinvm", user_config("myname", password="Use-strong-passwords!"), +#' size="Standard_DS4_v2", config="windows_2019", datadisks=500, ip=NULL, +#' location="australiaeast") #' -#" # Linux cluster with 5 nodes -#' sub$create_vm_cluster("myLinuxCluster", -#' location="australiaeast", -#' os="Linux", -#' username="ds", -#' passkey=readLines("~/id_rsa.pub"), -#' clust_size=5) +#' # Ubuntu DSVM, GPU-enabled +#' sub$create_vm("mydsvm", user_config("myname", "~/.ssh/id_rsa.pub"), size="Standard_NC12", +#' config="ubuntu_dsvm_ss", +#' location="australiaeast") +#' +#' ## custom VM configuration: Windows 10 Pro 1903 with data disks +#' ## this assumes you have a valid Win10 desktop license +#' user <- user_config("myname", password="Use-strong-passwords!") +#' image <- image_config( +#' publisher="MicrosoftWindowsDesktop", +#' offer="Windows-10", +#' sku="19h1-pro" +#' ) +#' datadisks <- list( +#' datadisk_config(250, type="Premium_LRS"), +#' datadisk_config(1000, type="Standard_LRS") +#' ) +#' nsg <- nsg_config( +#' list(nsg_rule_allow_rdp) +#' ) +#' config <- vm_config( +#' image=image, +#' keylogin=FALSE, +#' datadisks=datadisks, +#' nsg=nsg, +#' properties=list(licenseType="Windows_Client") +#' ) +#' sub$create_vm("mywin10vm", user, size="Standard_DS2_v2", config=config, +#' location="australiaeast") +#' +#' +#' # default Ubuntu scaleset: +#' # load balancer and autoscaler enabled, Standard_DS1_v2 +#' sub$create_vm_scaleset("mydsvmss", user_config("myname", "~/.ssh/id_rsa.pub"), +#' instances=5, +#' location="australiaeast")) +#' +#' # Ubuntu DSVM scaleset with public GPU-enabled instances, no load balancer or autoscaler +#' sub$create_vm_scaleset("mydsvmss", user_config("myname", "~/.ssh/id_rsa.pub"), +#' instances=5, size="Standard_NC12", config="ubuntu_dsvm_ss", +#' options=scaleset_options(public=TRUE), +#' load_balancer=NULL, autoscaler=NULL, +#' location="australiaeast") +#' +#' # RHEL scaleset, allow http/https access +#' sub$create_vm_scaleset("myrhelss", user_config("myname", "~/.ssh/id_rsa.pub"), +#' instances=5, config="rhel_8_ss", +#' nsg=nsg_config(list(nsg_rule_allow_http, nsg_rule_allow_https)), +#' location="australiaeast") +#' +#' # Large Debian scaleset, using low-priority VMs +#' # need to set the instance size to something that supports low-pri +#' sub$create_vm_scaleset("mydebss", user_config("myname", "~/.ssh/id_rsa.pub"), +#' instances=50, size="Standard_DS3_v2", config="debian_9_backports_ss", +#' options=scaleset_options(low_priority=TRUE, large_scaleset=TRUE), +#' location="australiaeast") +#' +#' +#' ## VM and scaleset in the same resource group and virtual network +#' # first, create the resgroup +#' rg <- sub$create_resource_group("rgname", "australiaeast") +#' +#' # create the master +#' rg$create_vm("mastervm", user_config("myname", "~/.ssh/id_rsa.pub")) +#' +#' # get the vnet resource +#' vnet <- rg$get_resource(type="Microsoft.Network/virtualNetworks", name="mastervm-vnet") +#' +#' # create the scaleset +#' rg$create_vm_scaleset("slavess", user_config("myname", "~/.ssh/id_rsa.pub"), +#' instances=5, vnet=vnet, nsg=NULL, load_balancer=NULL, autoscaler=NULL) #' #' } #' @rdname create_vm -#' @aliases create_vm create_vm_cluster +#' @aliases create_vm create_vm_scaleset #' @name create_vm NULL @@ -224,22 +271,17 @@ NULL #' resource_group = name) #' #' ## R6 method for class 'az_resource_group' -#' delete_vm_cluster(name, confirm = TRUE, free_resources = TRUE) +#' delete_vm_scaleset(name, confirm = TRUE, free_resources = TRUE) #' #' ## R6 method for class 'az_subscription' -#' delete_vm_cluster(name, confirm = TRUE, free_resources = TRUE, +#' delete_vm_scaleset(name, confirm = TRUE, free_resources = TRUE, #' resource_group = name) #' ``` #' @section Arguments: -#' - `name`: The name of the VM or cluster. +#' - `name`: The name of the VM or scaleset. #' - `confirm`: Whether to confirm the delete. #' - `free_resources`: If this was a deployed template, whether to free all resources created during the deployment process. -#' - `resource_group`: For the `AzureRMR::az_subscription` method, the resource group containing the VM or cluster. -#' -#' @section Details: -#' If the VM or cluster is of class [az_vm_template] and was created in exclusive mode, this method deletes the entire resource group that it occupies. This automatically frees all resources that were created during the deployment process. Otherwise, if `free_resources=TRUE`, it manually deletes each individual resource in turn. This is done synchronously (the method does not return until the deletion is complete) to allow for dependencies. -#' -#' If the VM is of class [az_vm_resource], this method only deletes the VM resource itself, not any other resources it may depend on. +#' - `resource_group`: For the `AzureRMR::az_subscription` method, the resource group containing the VM or scaleset. #' #' @seealso #' [create_vm], [az_vm_template], [az_vm_resource], @@ -248,16 +290,15 @@ NULL #' @examples #' \dontrun{ #' -#' sub <- AzureRMR::az_rm$ -#' new(tenant="myaadtenant.onmicrosoft.com", app="app_id", password="password")$ +#' sub <- AzureRMR::get_azure_login()$ #' get_subscription("subscription_id") -#' -#' sub$delete_vm("myWindowsDSVM") -#' sub$delete_vm("myLinuxDSVM") +#' +#' sub$delete_vm("myvm") +#' sub$delete_vm_scaleset("myscaleset") #' #' } #' @rdname delete_vm -#' @aliases delete_vm delete_vm_cluster +#' @aliases delete_vm delete_vm_scaleset #' @name delete_vm NULL @@ -267,6 +308,7 @@ NULL { add_sub_methods() add_rg_methods() + add_defunct_methods() } @@ -288,20 +330,8 @@ add_sub_methods <- function() else sapply(res$value, `[[`, "name") }) - - az_subscription$set("public", "create_vm", overwrite=TRUE, function(...) - { - self$create_vm_cluster(..., clust_size=1) - }) - - - az_subscription$set("public", "create_vm_cluster", overwrite=TRUE, - function(name, location, resource_group=name, - os=c("Windows", "Ubuntu"), size="Standard_DS3_v2", - username, passkey, userauth_type=c("password", "key"), - ext_file_uris=NULL, inst_command=NULL, - clust_size, template, parameters, - ..., wait=TRUE) + az_subscription$set("public", "create_vm", overwrite=TRUE, + function(name, ..., resource_group=name, location) { if(!is_resource_group(resource_group)) { @@ -320,11 +350,7 @@ add_sub_methods <- function() } else mode <- "Incremental" # if passed a resource group object, assume it already exists in Azure - res <- try(resource_group$create_vm_cluster(name, os=os, size=size, - username=username, passkey=passkey, userauth_type=userauth_type, - ext_file_uris=ext_file_uris, inst_command=inst_command, - clust_size=clust_size, template=template, parameters=parameters, - ..., wait=wait, mode=mode)) + res <- try(resource_group$create_vm(name, ..., mode=mode)) if(inherits(res, "try-error") && mode == "Complete") { @@ -334,6 +360,35 @@ add_sub_methods <- function() res }) + az_subscription$set("public", "create_vm_scaleset", overwrite=TRUE, + function(name, ..., resource_group=name, location) + { + if(!is_resource_group(resource_group)) + { + rgnames <- names(self$list_resource_groups()) + if(resource_group %in% rgnames) + { + resource_group <- self$get_resource_group(resource_group) + mode <- "Incremental" + } + else + { + message("Creating resource group '", resource_group, "'") + resource_group <- self$create_resource_group(resource_group, location=location) + mode <- "Complete" + } + } + else mode <- "Incremental" # if passed a resource group object, assume it already exists in Azure + + res <- try(resource_group$create_vm_scaleset(name, ..., mode=mode)) + + if(inherits(res, "try-error") && mode == "Complete") + { + resource_group$delete(confirm=FALSE) + stop("Unable to create VM scaleset", call.=FALSE) + } + res + }) az_subscription$set("public", "get_vm", overwrite=TRUE, function(name, resource_group=name) @@ -344,14 +399,13 @@ add_sub_methods <- function() resource_group$get_vm(name) }) - - az_subscription$set("public", "get_vm_cluster", overwrite=TRUE, + az_subscription$set("public", "get_vm_scaleset", overwrite=TRUE, function(name, resource_group=name) { if(!is_resource_group(resource_group)) resource_group <- self$get_resource_group(resource_group) - resource_group$get_vm_cluster(name) + resource_group$get_vm_scaleset(name) }) az_subscription$set("public", "delete_vm", overwrite=TRUE, @@ -363,41 +417,13 @@ add_sub_methods <- function() resource_group$delete_vm(name, confirm=confirm, free_resources=free_resources) }) - - az_subscription$set("public", "delete_vm_cluster", overwrite=TRUE, + az_subscription$set("public", "delete_vm_scaleset", overwrite=TRUE, function(name, confirm=TRUE, free_resources=TRUE, resource_group=name) { if(!is_resource_group(resource_group)) resource_group <- self$get_resource_group(resource_group) - resource_group$delete_vm_cluster(name, confirm=confirm, free_resources=free_resources) - }) - - - az_subscription$set("public", "list_vms", overwrite=TRUE, function() - { - provider <- "Microsoft.Compute" - path <- "virtualMachines" - api_version <- self$get_provider_api_version(provider, path) - - op <- file.path("providers", provider, path) - - cont <- call_azure_rm(self$token, self$id, op, api_version=api_version) - lst <- lapply(cont$value, - function(parms) AzureVM::az_vm_resource$new(self$token, self$id, deployed_properties=parms)) - # keep going until paging is complete - while(!is_empty(cont$nextLink)) - { - cont <- call_azure_url(self$token, cont$nextLink) - lst <- lapply(cont$value, - function(parms) AzureVM::az_vm_resource$new(self$token, self$id, deployed_properties=parms)) - } - - # namespace shenanigans: get unexported function from AzureVM - convert_to_vm_template <- get("convert_to_vm_template", loadNamespace("AzureVM")) - - # get templates corresponding to raw VMs (if possible) - lapply(named_list(lst), convert_to_vm_template) + resource_group$delete_vm_scaleset(name, confirm=confirm, free_resources=free_resources) }) } @@ -405,138 +431,146 @@ add_sub_methods <- function() # extend resource group methods add_rg_methods <- function() { - az_resource_group$set("public", "create_vm", overwrite=TRUE, function(...) + az_resource_group$set("public", "create_vm", overwrite=TRUE, + function(name, login_user, size="Standard_DS3_v2", config="ubuntu_18.04", managed=TRUE, datadisks=numeric(0), + ..., template, parameters, mode="Incremental", wait=TRUE) { - self$create_vm_cluster(..., clust_size=1) - }) + stopifnot(inherits(login_user, "user_config")) + if(is.character(config)) + config <- get(config, getNamespace("AzureVM")) + if(is.function(config)) + config <- config(!is_empty(login_user$key), managed, datadisks, ...) - az_resource_group$set("public", "create_vm_cluster", overwrite=TRUE, - function(name, os=c("Windows", "Ubuntu"), size="Standard_DS3_v2", - username, passkey, userauth_type=c("password", "key"), - ext_file_uris=NULL, inst_command=NULL, - clust_size, template, parameters, - ..., wait=TRUE) - { - # namespace shenanigans: get unexported functions from AzureVM - get_dsvm_template <- get("get_dsvm_template", loadNamespace("AzureVM")) - make_dsvm_param_list <- get("make_dsvm_param_list", loadNamespace("AzureVM")) + stopifnot(inherits(config, "vm_config")) - os <- match.arg(os) - userauth_type <- match.arg(userauth_type) - - if(missing(parameters) && (missing(username) || missing(passkey))) - stop("Must supply login username and password/private key", call.=FALSE) - - # find template given input args if(missing(template)) - template <- get_dsvm_template(os, userauth_type, clust_size, ext_file_uris, inst_command) + template <- build_template_definition(config) - # convert input args into parameter list for template if(missing(parameters)) - parameters <- make_dsvm_param_list(name=name, size=size, - username=username, userauth_type=userauth_type, passkey=passkey, - ext_file_uris=ext_file_uris, inst_command=inst_command, - clust_size=clust_size, template=template) + parameters <- build_template_parameters(config, name, login_user, size) - AzureVM::az_vm_template$new(self$token, self$subscription, self$name, name, - template=template, parameters=parameters, ..., wait=wait) + az_vm_template$new(self$token, self$subscription, self$name, name, + template=template, parameters=parameters, mode=mode, wait=wait) }) + az_resource_group$set("public", "create_vm_scaleset", overwrite=TRUE, + function(name, login_user, instances, size="Standard_DS1_v2", config="ubuntu_18.04_ss", + ..., template, parameters, mode="Incremental", wait=TRUE) + { + stopifnot(inherits(login_user, "user_config")) + + if(is.character(config)) + config <- get(config, getNamespace("AzureVM")) + if(is.function(config)) + config <- config(...) + + stopifnot(inherits(config, "vmss_config")) + + if(missing(template)) + template <- build_template_definition(config) + + if(missing(parameters)) + parameters <- build_template_parameters(config, name, login_user, size, instances) + + az_vmss_template$new(self$token, self$subscription, self$name, name, + template=template, parameters=parameters, mode=mode, wait=wait) + }) az_resource_group$set("public", "get_vm", overwrite=TRUE, function(name) { - res <- try(AzureVM::az_vm_template$new(self$token, self$subscription, self$name, name), silent=TRUE) - - # if we couldn't find a VM deployment template, get the raw VM resource - if(inherits(res, "try-error")) - { - warning("No deployment template found for VM '", name, "'", call.=FALSE) - res <- AzureVM::az_vm_resource$new(self$token, self$subscription, self$name, - type="Microsoft.Compute/virtualMachines", name=name) - } - res + az_vm_template$new(self$token, self$subscription, self$name, name) }) - - az_resource_group$set("public", "get_vm_cluster", overwrite=TRUE, + az_resource_group$set("public", "get_vm_scaleset", overwrite=TRUE, function(name) { - AzureVM::az_vm_template$new(self$token, self$subscription, self$name, name) + az_vmss_template$new(self$token, self$subscription, self$name, name) }) - az_resource_group$set("public", "delete_vm", overwrite=TRUE, function(name, confirm=TRUE, free_resources=TRUE) { - vm <- self$get_vm(name) - if(is_vm_template(vm)) - vm$delete(confirm=confirm, free_resources=free_resources) - else vm$delete(confirm=confirm) + self$get_vm(name)$delete(confirm=confirm, free_resources=free_resources) }) - - az_resource_group$set("public", "delete_vm_cluster", overwrite=TRUE, + az_resource_group$set("public", "delete_vm_scaleset", overwrite=TRUE, function(name, confirm=TRUE, free_resources=TRUE) { - self$get_vm_cluster(name)$delete(confirm=confirm, free_resources=free_resources) + self$get_vm_scaleset(name)$delete(confirm=confirm, free_resources=free_resources) }) - - az_resource_group$set("public", "list_vms", overwrite=TRUE, function() - { - provider <- "Microsoft.Compute" - path <- "virtualMachines" - api_version <- az_subscription$ - new(self$token, self$subscription)$ - get_provider_api_version(provider, path) - - op <- file.path("resourceGroups", self$name, "providers", provider, path) - - cont <- call_azure_rm(self$token, self$subscription, op, api_version=api_version) - lst <- lapply(cont$value, - function(parms) AzureVM::az_vm_resource$new(self$token, self$subscription, deployed_properties=parms)) - - # keep going until paging is complete - while(!is_empty(cont$nextLink)) - { - cont <- call_azure_url(self$token, cont$nextLink) - lst <- lapply(cont$value, - function(parms) AzureVM::az_vm_resource$new(self$token, self$subscription, deployed_properties=parms)) - } - - # namespace shenanigans: get unexported function from AzureVM - convert_to_vm_template <- get("convert_to_vm_template", loadNamespace("AzureVM")) - - # get templates corresponding to raw VMs (if possible) - lapply(named_list(lst), convert_to_vm_template) - }) - - az_resource_group$set("public", "list_vm_sizes", overwrite=TRUE, function(name_only=FALSE) { az_subscription$ - new(self$token, self$subscription)$ + new(self$token, parms=list(subscriptionId=self$subscription))$ list_vm_sizes(self$location, name_only=name_only) }) + + az_resource_group$set("public", "get_vm_resource", overwrite=TRUE, + function(name) + { + az_vm_resource$new(self$token, self$subscription, self$name, + type="Microsoft.Compute/virtualMachines", name=name) + }) + + az_resource_group$set("public", "get_vm_scaleset_resource", overwrite=TRUE, + function(name) + { + az_vmss_resource$new(self$token, self$subscription, self$name, + type="Microsoft.Compute/virtualMachineScalesets", name=name) + }) } -convert_to_vm_template <- function(vm_resource) -{ - token <- vm_resource$token - subscription <- vm_resource$subscription - resource_group <- vm_resource$resource_group - name <- vm_resource$name - tpl <- try(AzureVM::az_vm_template$new(token, subscription, resource_group, name), silent=TRUE) - if(!inherits(tpl, "try-error") && - !is_empty(tpl$properties$outputResources) && - grepl(sprintf("providers/Microsoft.Compute/virtualMachines/%s$", name), - tpl$properties$outputResources[[1]]$id, ignore.case=TRUE)) - tpl - else vm_resource +#' Defunct methods +#' +#' @section Usage: +#' ``` +#' get_vm_cluster(...) +#' create_vm_cluster(...) +#' delete_vm_cluster(...) +#' ``` +#' These methods for the `az_subscription` and `az_resource_group` classes are defunct in AzureVM 2.0. To work with virtual machine clusters, call the [get_vm_scaleset], [create_vm_scaleset] and [delete_vm_scaleset] methods instead. +#' @rdname defunct +#' @name defunct +#' @aliases get_vm_cluster create_vm_cluster delete_vm_cluster +NULL + + +add_defunct_methods <- function() +{ + az_subscription$set("public", "get_vm_cluster", overwrite=TRUE, function(...) + { + .Defunct(msg="The 'get_vm_cluster' method is defunct.\nUse 'get_vm_scaleset' instead.") + }) + + az_subscription$set("public", "create_vm_cluster", overwrite=TRUE, function(...) + { + .Defunct(msg="The 'create_vm_cluster' method is defunct.\nUse 'create_vm_scaleset' instead.") + }) + + az_subscription$set("public", "delete_vm_cluster", overwrite=TRUE, function(...) + { + .Defunct(msg="The 'delete_vm_cluster' method is defunct.\nUse 'delete_vm_scaleset' instead.") + }) + + az_resource_group$set("public", "get_vm_cluster", overwrite=TRUE, function(...) + { + .Defunct(msg="The 'get_vm_cluster' method is defunct.\nUse 'get_vm_scaleset' instead.") + }) + + az_resource_group$set("public", "create_vm_cluster", overwrite=TRUE, function(...) + { + .Defunct(msg="The 'create_vm_cluster' method is defunct.\nUse 'create_vm_scaleset' instead.") + }) + + az_resource_group$set("public", "delete_vm_cluster", overwrite=TRUE, function(...) + { + .Defunct(msg="The 'delete_vm_cluster' method is defunct.\nUse 'delete_vm_scaleset' instead.") + }) } diff --git a/R/autoscaler_config.R b/R/autoscaler_config.R new file mode 100644 index 0000000..e949d07 --- /dev/null +++ b/R/autoscaler_config.R @@ -0,0 +1,81 @@ +#' Autoscaler configuration +#' +#' @param profiles A list of autoscaling profiles, each obtained via a call to `autoscaler_profile`. +#' @param ... Other named arguments that will be treated as resource properties. +#' @param name For `autoscaler_profile`, a name for the profile. +#' @param minsize,maxsize,default For `autoscaler_profile`, the minimum, maximum and default number of instances. +#' @param scale_out,scale_in For `autoscaler_profile`, the percentage CPU at which to scale out and in, respectively. +#' @param interval For `autoscaler_profile`, The interval between samples, in ISO 8601 format. The default is 1 minute. +#' @param window For `autoscaler_profile`, the window width over which to compute the percentage CPU. The default is 5 minutes. +#' +#' @seealso +#' [create_vm_scaleset], [vmss_config] +#' @export +autoscaler_config <- function(profiles=list(autoscaler_profile()), ...) +{ + props <- list(profiles=profiles, ...) + structure(list(properties=props), class="as_config") +} + + +build_resource_fields.as_config <- function(config, ...) +{ + config$properties$profiles <- lapply(config$properties$profiles, unclass) + utils::modifyList(as_default, config) +} + + +add_template_variables.as_config <- function(config, ...) +{ + name <- "[concat(parameters('vmName'), '-as')]" + id <- "[resourceId('Microsoft.Insights/autoscaleSettings', variables('asName'))]" + ref <- "[concat('Microsoft.Insights/autoscaleSettings/', variables('asName'))]" + capacity <- "[mul(int(parameters('instanceCount')), 10)]" + scaleval <- "[max(div(int(parameters('instanceCount')), 5), 1)]" + list(asName=name, asId=id, asRef=ref, asMaxCapacity=capacity, asScaleValue=scaleval) +} + + +#' @rdname autoscaler_config +#' @export +autoscaler_profile <- function(name="Profile", minsize=1, maxsize=NA, default=NA, scale_out=0.75, scale_in=0.25, + interval="PT1M", window="PT5M") +{ + if(is.na(maxsize)) + maxsize <- "[variables('asMaxCapacity')]" + if(is.na(default)) + default <- "[parameters('instanceCount')]" + capacity <- list(minimum=minsize, maximum=maxsize, default=default) + + trigger <- list( + metricName="Percentage CPU", + metricNamespace="", + metricResourceUri="[variables('vmId')]", + timeGrain=interval, + timeWindow=window, + statistic="Average", + timeAggregation="Average" + ) + action <- list( + type="ChangeCount", + value="[variables('asScaleValue')]", + cooldown=interval + ) + + rule_out <- list(metricTrigger=trigger, scaleAction=action) + rule_out$metricTrigger$operator <- "GreaterThan" + rule_out$metricTrigger$threshold <- round(scale_out * 100) + rule_out$scaleAction$direction <- "Increase" + + rule_in <- list(metricTrigger=trigger, scaleAction=action) + rule_in$metricTrigger$operator <- "LessThan" + rule_in$metricTrigger$threshold <- round(scale_in * 100) + rule_in$scaleAction$direction <- "Decrease" + + prof <- list( + name=name, + capacity=capacity, + rules=list(rule_out, rule_in) + ) + structure(prof, class="as_profile_config") +} diff --git a/R/az_vm_resource.R b/R/az_vm_resource.R index 9850101..99b2d36 100644 --- a/R/az_vm_resource.R +++ b/R/az_vm_resource.R @@ -5,25 +5,29 @@ #' @docType class #' @section Methods: #' The following methods are available, in addition to those provided by the [AzureRMR::az_resource] class: -#' - `new(...)`: Initialize a new VM object. #' - `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()`: Update the VM status fields in this object with information from the host. -#' - `resize(size, deallocate=FALSE, wait=FALSE)`: Resize the VM. Optionally deallocate it first (may sometimes be necessary). +#' - `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). +#' - `get_public_ip_address(nic=1, config=1)`: Get the public IP address of the VM. Returns NA if the VM is shut down, or is not publicly accessible. +#' - `get_private_ip_address(nic=1, config=1)`: Get the private IP address of the VM. +#' - `add_extension(publisher, type, version, settings=list(), protected_settings=list(), key_vault_settings=list())`: Add an extension to the VM. +#' - `do_vm_operation(...)`: Carry out an arbitrary operation on the VM resource. See the `do_operation` method of the [AzureRMR::az_resource] class for more details. #' #' @seealso -#' [AzureRMR::az_resource], +#' [AzureRMR::az_resource], [get_vm_resource], [az_vm_template] +#' #' [VM API reference](https://docs.microsoft.com/en-us/rest/api/compute/virtualmachines) #' @format An R6 object of class `az_vm_resource`, inheriting from `AzureRMR::az_resource`. #' @export az_vm_resource <- R6::R6Class("az_vm_resource", inherit=AzureRMR::az_resource, public=list( - disks=NULL, status=NULL, + nic_api_version="2019-04-01", # need to record this since AzureRMR can't currently get API versions for subresources sync_vm_status=function() { @@ -36,30 +40,26 @@ public=list( self$sync_fields() - res <- self$do_operation("instanceView", http_verb="GET") + res <- self$do_operation("instanceView") self$status <- get_status(res$statuses) - disks <- named_list(res$disks) - self$disks <- lapply(disks, function(d) get_status(d$status)) - - invisible(NULL) + self$status }, start=function(wait=TRUE) { - message("Starting VM '", self$name, "'") self$do_operation("start", http_verb="POST") - Sys.sleep(2) + # Sys.sleep(2) if(wait) { for(i in 1:100) { + Sys.sleep(5) self$sync_vm_status() if(length(self$status) == 2 && self$status[1] == "succeeded" && self$status[2] == "running") break - Sys.sleep(5) } if(length(self$status) < 2 || self$status[1] != "succeeded" || @@ -70,19 +70,18 @@ public=list( restart=function(wait=TRUE) { - message("Restarting VM '", self$name, "'") self$do_operation("restart", http_verb="POST") - Sys.sleep(2) + # Sys.sleep(2) if(wait) { for(i in 1:100) { + Sys.sleep(5) self$sync_vm_status() if(length(self$status) == 2 && self$status[1] == "succeeded" && self$status[2] == "running") break - Sys.sleep(5) } if(length(self$status) < 2 || self$status[1] != "succeeded" || @@ -93,12 +92,6 @@ public=list( stop=function(deallocate=TRUE, wait=FALSE) { - msg <- "Shutting down" - if(deallocate) - msg <- paste(msg, "and deallocating") - msg <- paste0(msg, " VM '", self$name, "'") - message(msg) - self$do_operation("powerOff", http_verb="POST") if(deallocate) self$do_operation("deallocate", http_verb="POST") @@ -106,21 +99,16 @@ public=list( { for(i in 1:100) { + Sys.sleep(5) self$sync_vm_status() if(length(self$status) < 2 || self$status[2] %in% c("stopped", "deallocated")) break - Sys.sleep(5) } if(length(self$status) == 2 && !(self$status[2] %in% c("stopped", "deallocated"))) stop("Unable to shut down VM", call.=FALSE) } }, - add_extension=function(...) - { - stop("This function is not yet implemented", call.=FALSE) - }, - resize=function(size, deallocate=FALSE, wait=FALSE) { if(deallocate) @@ -144,15 +132,13 @@ public=list( } }, - run_deployed_command=function(command=NULL, parameters=NULL, script=NULL) + run_deployed_command=function(command, parameters=NULL, script=NULL) { - if(is_empty(command)) - stop("Must supply a command to run", call.=FALSE) body <- list(commandId=command, parameters=parameters, script=script) self$do_operation("runCommand", body=body, encode="json", http_verb="POST") }, - run_script=function(script=NULL, parameters=NULL) + run_script=function(script, parameters=NULL) { os_prof_names <- names(self$properties$osProfile) windows <- any(grepl("windows", os_prof_names, ignore.case=TRUE)) @@ -164,6 +150,45 @@ public=list( self$run_deployed_command(cmd, as.list(parameters), as.list(script)) }, + get_public_ip_address=function(nic=1, config=1) + { + nic <- private$get_nic(nic) + ip_id <- nic$properties$ipConfigurations[[config]]$properties$publicIPAddress$id + if(is_empty(ip_id)) + return(NA_character_) + ip <- az_resource$new(self$token, self$subscription, id=ip_id)$properties$ipAddress + if(is.null(ip)) + NA_character_ + else ip + }, + + get_private_ip_address=function(nic=1, config=1) + { + nic <- private$get_nic(nic) + nic$properties$ipConfigurations[[config]]$properties$privateIPAddress + }, + + add_extension=function(publisher, type, version, settings=list(), + protected_settings=list(), key_vault_settings=list()) + { + name <- gsub("[[:punct:]]", "", type) + op <- file.path("extensions", name) + props <- list( + publisher=publisher, + type=type, + typeHandlerVersion=version, + autoUpgradeMinorVersion=TRUE, + settings=settings + ) + + if(!is_empty(protected_settings)) + props$protectedSettings <- protected_settings + if(!is_empty(key_vault_settings)) + props$protectedSettingsFromKeyVault <- key_vault_settings + + self$do_operation(op, body=list(properties=props), http_verb="PUT") + }, + print=function(...) { cat("\n", sep="") @@ -179,41 +204,22 @@ public=list( cat("---\n") cat(AzureRMR::format_public_fields(self, - exclude=c("subscription", "resource_group", "type", "name", "status", "is_synced"))) + exclude=c("subscription", "resource_group", "type", "name", "status", "is_synced", "nic_api_version"))) cat(AzureRMR::format_public_methods(self)) invisible(NULL) - }, - - # add custom deletion method to handle managed disks - delete=function(..., confirm=TRUE, wait=TRUE) - { - managed_disks <- c( - self$properties$storageProfile$osDisk$managedDisk$id, - lapply(self$properties$storageProfile$dataDisks, - function(x) x$managedDisk$id) - ) - - super$delete(..., confirm=confirm, wait=wait) - if(!is_empty(managed_disks)) - { - md_api_ver <- named_list( - call_azure_rm(self$token, self$subscription, "providers/Microsoft.Compute")$ - resourceTypes, "resourceType" - )$ - disks$ - apiVersions[[1]] - - lapply(managed_disks, function(id) - az_resource$ - new(self$token, self$subscription, id=id, deployed_properties=list(NULL))$ - delete(confirm=confirm, wait=wait) - ) - } } ), private=list( + get_nic=function(n=1) + { + nic_id <- self$properties$networkProfile$networkInterfaces[[n]]$id + if(is_empty(nic_id)) + stop("Network interface resource not found", call.=FALSE) + az_resource$new(self$token, self$subscription, id=nic_id, api_version=self$nic_api_version) + }, + init_and_deploy=function(...) { stop("Do not use 'az_vm_resource' to create a new VM", call.=FALSE) diff --git a/R/az_vm_template.R b/R/az_vm_template.R index 62c2d4b..9de85ee 100644 --- a/R/az_vm_template.R +++ b/R/az_vm_template.R @@ -1,55 +1,45 @@ -#' Virtual machine cluster template class +#' Virtual machine template class #' -#' Class representing a virtual machine template. This class keeps track of all resources that are created as part of deploying a VM or cluster of VMs, and exposes methods for managing them. In this page, "VM" refers to both a cluster of virtual machines, as well as a single virtual machine (which is treated as the special case of a cluster containing a single node). +#' 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 Methods: -#' The following methods are available, in addition to those provided by the [AzureRMR::az_template] class: -#' - `new(...)`: Initialize a new VM object. See 'Initialization' for more details. +#' 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()`: Update the VM status fields in this object with information from the host. -#' - `resize(size, deallocate=FALSE, wait=FALSE)`: Resize the VM. Optionally deallocate it first (may sometimes be necessary). -#' -#' @section Fields: -#' The following fields are available, in addition to those provided by the `AzureRMR::az_template` class. Each is a list with one element per node in the cluster. -#' - `disks`: The status of any attached disks. -#' - `ip_address`: The IP address. NULL if the node is currently deallocated. -#' - `dns_name`: The fully qualified domain name. -#' - `status`: The status of the node, giving the provisioning state and power state. +#' - `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). +#' - `get_public_ip_address(nic=1, config=1)`: Get the public IP address of the VM. Returns NULL 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. +#' - `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. #' #' @details -#' A single virtual machine in Azure is actually a collection of resources, including any and all of the following. A cluster can share a storage account and virtual network, but each individual node will still have its own IP address and network interface. -#' - Storage account -#' - Network interface -#' - Network security group -#' - Virtual network -#' - IP address -#' - The VM itself +#' The VM operations listed above are actually provided by the [az_vm_resource] class, and propagated to the template as active bindings. +#' +#' 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. #' -#' @section Initialization: -#' Initializing a new object of this class can either retrieve an existing VM template, or deploy a new VM template on the host. Generally, the best way to initialize an object is via the VM-related methods of the [az_subscription] and [az_resource_group] class, which handle the details automatically. -#' -#' A new VM can be created in _exclusive_ mode, meaning a new resource group is created solely to hold the VM. This simplifies deleting a VM considerably, as deleting the resource group will also automatically delete all the VM's resources. This can be done asynchronously, meaning that the `delete()` method returns immediately while the process continues on the host. Otherwise, deleting a VM will explicitly delete each of its resources, a task that must be done synchronously to allow for dependencies. -#' #' @seealso -#' [AzureRMR::az_resource], [create_vm], [create_vm_cluster], [get_vm], [get_vm_cluster], [list_vms], -#' [delete_vm], [delete_vm_cluster], +#' [AzureRMR::az_template], [create_vm], [get_vm], [delete_vm] +#' #' [VM API reference](https://docs.microsoft.com/en-us/rest/api/compute/virtualmachines) #' #' @examples #' \dontrun{ #' -#' # recommended way to retrieve a VM: via a resource group or subscription object -#' sub <- AzureRMR::az_rm$ -#' new(tenant="myaadtenant.onmicrosoft.com", app="app_id", password="password")$ +#' sub <- AzureRMR::get_azure_login()$ #' get_subscription("subscription_id") -#' +#' #' vm <- sub$get_vm("myLinuxDSVM") #' #' # start the VM @@ -73,278 +63,129 @@ az_vm_template <- R6::R6Class("az_vm_template", inherit=az_template, public=list( - disks=NULL, - status=NULL, - ip_address=NULL, dns_name=NULL, - clust_size=NULL, initialize=function(token, subscription, resource_group, name, ..., wait=TRUE) { super$initialize(token, subscription, resource_group, name, ..., wait=wait) - # fill in fields that don't require querying the host - num_instances <- self$properties$outputs$numInstances - if(is_empty(num_instances)) - { - self$clust_size <- 1 - vmnames <- self$name - } - else - { - self$clust_size <- as.numeric(num_instances$value) - vmnames <- paste0(self$name, seq_len(self$clust_size) - 1) - } - if(wait) { - private$vm <- sapply(vmnames, function(name) - { - az_vm_resource$new(self$token, self$subscription, self$resource_group, - type="Microsoft.Compute/virtualMachines", name=name) - }, simplify=FALSE) + 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) - ip <- lapply(ip_id, function(id) - az_resource$new(self$token, self$subscription, id=id)$properties) - self$ip_address <- sapply(ip, function(x) x$ipAddress) - self$dns_name <- sapply(ip, function(x) x$dnsSettings$fqdn) - - lapply(private$vm, function(obj) obj$sync_vm_status()) - self$disks <- lapply(private$vm, "[[", "disks") - self$status <- lapply(private$vm, "[[", "status") - NULL - } - else message("Deployment started. Call the sync_vm_status() method to track the status of the deployment.") - - NULL - }, - - sync_vm_status=function() - { - if(is_empty(private$vm) || is_empty(self$status) || tolower(self$status[[1]][1]) != "succeeded") - { - res <- try(self$initialize(self$token, self$subscription, self$resource_group, self$name), silent=TRUE) - if(inherits(res, "try-error")) + if(!is_empty(ip_id)) { - message("VM deployment in progress") - return(invisible(NULL)) + ip <- az_resource$new(self$token, self$subscription, id=ip_id) + self$dns_name <- ip$properties$dnsSettings$fqdn } } - - lapply(private$vm, function(obj) obj$sync_vm_status()) - self$disks <- lapply(private$vm, "[[", "disks") - self$status <- lapply(private$vm, "[[", "status") - self$status - }, - - start=function(wait=TRUE) - { - lapply(private$get_vm(), function(obj) obj$start(wait=wait)) - self$sync_vm_status() - }, - - stop=function(deallocate=TRUE, wait=TRUE) - { - lapply(private$get_vm(), function(obj) obj$stop(deallocate=deallocate, wait=wait)) - self$sync_vm_status() - }, - - restart=function(wait=TRUE) - { - lapply(private$get_vm(), function(obj) obj$restart(wait=wait)) - self$sync_vm_status() - }, - - add_extension=function(...) - { - lapply(private$get_vm(), function(obj) obj$add_extension(...)) - invisible(NULL) - }, - - resize=function(size, deallocate=FALSE, wait=FALSE) - { - lapply(private$get_vm(), function(obj) obj$resize(size, deallocate=deallocate, wait=wait)) - invisible(NULL) - }, - - run_deployed_command=function(...) - { - lapply(private$get_vm(), function(obj) obj$run_deployed_command(...)) - invisible(NULL) - }, - - run_script=function(...) - { - lapply(private$get_vm(), function(obj) obj$run_script(...)) - invisible(NULL) + else message("Deployment started. Call the sync_vm_status() method to track the status of the deployment.") }, delete=function(confirm=TRUE, free_resources=TRUE) { - # delete the resource group -- customised confirmation message - if(self$properties$mode == "Complete" && confirm && interactive()) - { - vmtype <- if(self$clust_size == 1) "VM" else "VM cluster" - msg <- paste0("Do you really want to delete ", vmtype, " and resource group '", self$name, "'? (y/N) ") - yn <- readline(msg) - if(tolower(substr(yn, 1, 1)) != "y") - return(invisible(NULL)) - super$delete(confirm=FALSE, free_resources=TRUE) - } - else - { - if(free_resources) - { - # delete individual resources - - if(confirm && interactive()) - { - vmtype <- if(self$clust_size == 1) "VM" else "VM cluster" - msg <- paste0("Do you really want to delete ", vmtype, " '", self$name, "'? (y/N) ") - yn <- readline(msg) - if(tolower(substr(yn, 1, 1)) != "y") - return(invisible(NULL)) - } - - lapply(private$vm, function(obj) obj$delete(confirm=FALSE, wait=TRUE)) - super$delete(confirm=FALSE, free_resources=TRUE) - } - else super$delete(confirm=confirm, free_resources=FALSE) - } + # 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(...) { - header <- " 1) - header <- paste0(header, "cluster ") - cat(header, self$name, ">\n", sep="") + cat("\n", sep="") - osProf <- names(private$vm[[1]]$properties$osProfile) + osProf <- names(private$vm$properties$osProfile) os <- if(any(grepl("linux", osProf))) "Linux" else if(any(grepl("windows", osProf))) "Windows" else "" exclusive <- self$properties$mode == "Complete" - dns_label <- if(self$clust_size == 1) "Domain name:" else "Domain names:" - dns_names <- if(is_empty(self$dns_name)) - paste0(" ", dns_label, " ") - else strwrap(paste(dns_label, paste0(self$dns_name, collapse=", ")), - width=0.8*getOption("width"), indent=2, exdent=4) + status <- if(is_empty(private$vm$status)) + "" + else paste0(names(private$vm$status), "=", private$vm$status, collapse=", ") cat(" Operating system:", os, "\n") cat(" Exclusive resource group:", exclusive, "\n") - cat(paste0(dns_names, collapse="\n"), "\n", sep="") - cat(" Status:") - - if(is_empty(self$status) || is_empty(self$status[[1]])) - cat(" \n") - else - { - prov_status <- as.data.frame(do.call(rbind, self$status)) - row.names(prov_status) <- paste0(" ", row.names(prov_status)) - cat("\n") - print(prov_status) - } - + cat(" Domain name:", self$dns_name, "\n") + cat(" Status:", status, "\n") cat("---\n") - exclude <- c("subscription", "resource_group", "name", "dns_name", "status") - if(self$clust_size == 1) - exclude <- c(exclude, "clust_size") + 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( + + 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, + + do_vm_operation=function() + private$vm$do_operation +), + private=list( - vm=list(NULL), + vm=NULL, - get_vm=function() + reorder_for_delete=function() { - if(is_empty(private$vm)) - stop("VM deployment in progress", call.=FALSE) - private$vm - }, - - sync_vm_resources=function() - { - private$vm <- sapply(vmnames, function(name) + is_type <- function(id, type) { - az_vm_resource$new(self$token, self$subscription, self$resource_group, - type="Microsoft.Compute/virtualMachines", name=name) - }, simplify=FALSE) + grepl(type, id, fixed=TRUE) + } - # get the hostname/IP address for the VM - outputs <- unlist(self$properties$outputResources) - ip_id <- grep("publicIPAddresses/.+$", outputs, ignore.case=TRUE, value=TRUE) - ip <- lapply(ip_id, function(id) - az_resource$new(self$token, self$subscription, id=id)$properties) + # 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))) - self$ip_address <- sapply(ip, function(x) x$ipAddress) - self$dns_name <- sapply(ip, function(x) x$dnsSettings$fqdn) + 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 + }) - lapply(private$vm, function(obj) obj$sync_vm_status()) - self$disks <- lapply(private$vm, "[[", "disks") - self$status <- lapply(private$vm, "[[", "status") - NULL + outs <- outs[order(new_order)] + self$properties$outputResources <- lapply(outs, function(x) list(id=x)) } )) - -#' Is an object an Azure VM template -#' -#' @param object an R object. -#' -#' @details -#' This function returns TRUE only for an object representing a VM template deployment. In particular, it returns FALSE for a raw VM resource. -#' -#' @return -#' A boolean. -#' @export -is_vm_template <- function(object) -{ - R6::is.R6(object) && inherits(object, "az_vm_template") -} - - -# arguments to this must be named -make_dsvm_param_list=function(...) -{ - params <- list(...) - - template <- tools::file_path_sans_ext(basename(params$template)) - parm_map <- param_mappings[[template]] - - # match supplied arguments to those expected by template - params <- params[names(params) %in% names(parm_map)] - names(params) <- parm_map[match(names(parm_map), names(params))] - - lapply(params, function(x) list(value=as.character(x))) -} - - -get_dsvm_template=function(os, userauth_type, clust_size, ext_file_uris, inst_command) -{ - if(os == "Ubuntu") - template <- "ubuntu_dsvm" - else if(os == "Windows") - template <- "win2016_dsvm" - else stop("Unknown OS: ", os, call.=FALSE) - - if(clust_size > 1) - template <- paste0(template, "_cl") - - if(userauth_type == "key") - template <- paste0(template, "_key") - - if(!is_empty(ext_file_uris) || !is_empty(inst_command)) - template <- paste0(template, "_ext") - - template <- system.file("templates", paste0(template, ".json"), package="AzureVM") - if(template == "") - stop("Unsupported combination of parameters", call.=FALSE) - template -} diff --git a/R/az_vmss_resource.R b/R/az_vmss_resource.R new file mode 100644 index 0000000..d57f95b --- /dev/null +++ b/R/az_vmss_resource.R @@ -0,0 +1,238 @@ +#' Virtual machine scaleset resource class +#' +#' Class representing a virtual machine scaleset resource. In general, the methods in this class should not be called directly, nor should objects be directly instantiated from it. Use the `az_vmss_template` class for interacting with scalesets instead. +#' +#' @docType class +#' @section Methods: +#' The following methods are available, in addition to those provided by the [AzureRMR::az_template] class. +#' - `sync_vmss_status`: Check the status of the scaleset. +#' - `list_instances()`: Return a list of [az_vm_resource] objects, one for each VM instance in the scaleset. Note that if the scaleset has a load balancer attached, the number of instances will vary depending on the load. +#' - `get_instance(id)`: Return a specific VM instance in the scaleset. +#' - `start(id=NULL, wait=FALSE)`: Start the scaleset. In this and the other methods listed here, `id` can be an optional character vector of instance IDs; if supplied, only carry out the operation for those instances. +#' - `restart(id=NULL, wait=FALSE)`: Restart the scaleset. +#' - `stop(deallocate=TRUE, id=NULL, wait=FALSE)`: Stop the scaleset. +#' - `get_public_ip_address()`: Get the public IP address of the scaleset (technically, of the load balancer). If the scaleset doesn't have a load balancer attached, returns NA. +#' - `get_vm_public_ip_addresses(id=NULL, nic=1, config=1)`: Get the public IP addresses for the instances in the scaleset. Returns NA if the instances are not publicly accessible. +#' - `get_vm_private_ip_addresses(id=NULL, nic=1, config=1)`: Get the private IP addresses for the instances in the scaleset. +#' - `run_deployed_command(command, parameters=NULL, script=NULL, id=NULL)`: Run a PowerShell command on the instances in the scaleset. +#' - `run_script(script, parameters=NULL, id=NULL)`: 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. +#' - `reimage(id=NULL, datadisks=FALSE)`: Reimage the instances in the scaleset. If `datadisks` is TRUE, reimage any attached data disks as well. +#' - `redeploy(id=NULL)`: Redeploy the instances in the scaleset. +#' - `mapped_vm_operation(..., id=NULL)`: Carry out an arbitrary operation on the instances in the scaleset. See the `do_operation` method of the [AzureRMR::az_resource] class for more details. +#' - `add_extension(publisher, type, version, settings=list(), protected_settings=list(), key_vault_settings=list())`: Add an extension to the scaleset. +#' - `do_vmss_operation(...)` Carry out an arbitrary operation on the scaleset resource (as opposed to the instances in the scaleset). +#' +#' @seealso +#' [AzureRMR::az_resource], [get_vm_scaleset_resource], [az_vmss_template] +#' +#' [VM scaleset API reference](https://docs.microsoft.com/en-us/rest/api/compute/virtualmachinescalesets) +#' @format An R6 object of class `az_vmss_resource`, inheriting from `AzureRMR::az_resource`. +#' @export +az_vmss_resource <- R6::R6Class("az_vmss_resource", inherit=AzureRMR::az_resource, + +public=list( + status=NULL, + + sync_vmss_status=function(id=NULL) + { + instances <- self$list_instances() + if(!is.null(id)) + instances <- instances[as.character(id)] + + statuses <- private$vm_map(id, function(res) + { + status <- res$sync_vm_status() + if(length(status) < 2) + status <- c(status, NA) + status + }) + + self$status <- data.frame(id=names(statuses), do.call(rbind, statuses), stringsAsFactors=FALSE) + colnames(self$status) <- c("id", "ProvisioningState", "PowerState") + row.names(self$status) <- NULL + self$status + }, + + list_instances=function() + { + lst <- named_list(get_paged_list(self$do_operation("virtualMachines")), "instanceId") + lapply(lst, private$make_vm_resource) + }, + + get_instance=function(id) + { + obj <- self$do_operation(file.path("virtualMachines", id)) + private$make_vm_resource(obj) + }, + + start=function(id=NULL, wait=FALSE) + { + body <- if(!is.null(id)) list(instanceIds=I(as.character(id))) else NULL + self$do_operation("start", body=body, http_verb="POST") + + if(wait) + { + for(i in 1:100) + { + Sys.sleep(5) + status <- self$sync_vmss_status(id) + if(all(status$PowerState == "running")) + break + } + if(!all(status$PowerState == "running")) + stop("Unable to start VM scaleset", call.=FALSE) + } + }, + + restart=function(id=NULL, wait=FALSE) + { + body <- if(!is.null(id)) list(instanceIds=I(as.character(id))) else NULL + self$do_operation("restart", body=body, http_verb="POST") + + if(wait) + { + for(i in 1:100) + { + Sys.sleep(5) + status <- self$sync_vmss_status(id) + if(all(status$PowerState == "running")) + break + } + if(!all(status$PowerState == "running")) + stop("Unable to restart VM scaleset", call.=FALSE) + } + }, + + stop=function(deallocate=TRUE, id=NULL, wait=FALSE) + { + body <- if(!is.null(id)) list(instanceIds=I(as.character(id))) else NULL + self$do_operation("powerOff", body=body, http_verb="POST") + if(deallocate) + self$do_operation("deallocate", body=body, http_verb="POST") + + if(wait) + { + for(i in 1:100) + { + Sys.sleep(5) + status <- self$sync_vm_status(id) + if(all(status$PowerState %in% c("stopped", "deallocated"))) + break + } + if(length(self$status) == 2 && !(self$status[2] %in% c("stopped", "deallocated"))) + stop("Unable to shut down VM", call.=FALSE) + } + }, + + get_vm_public_ip_addresses=function(id=NULL, nic=1, config=1) + { + unlist(private$vm_map(id, function(vm) vm$get_public_ip_address(nic, config))) + }, + + get_vm_private_ip_addresses=function(id=NULL, nic=1, config=1) + { + unlist(private$vm_map(id, function(vm) vm$get_private_ip_address(nic, config))) + }, + + run_deployed_command=function(command, parameters=NULL, script=NULL, id=NULL) + { + private$vm_map(id, function(vm) vm$run_deployed_command(command, parameters, script)) + }, + + run_script=function(script, parameters=NULL, id=NULL) + { + private$vm_map(id, function(vm) vm$run_script(script, parameters)) + }, + + reimage=function(id=NULL, datadisks=FALSE) + { + op <- if(datadisks) "reimageall" else "reimage" + if(is.null(id)) + self$do_operation(op, http_verb="POST") + else private$vm_map(id, function(vm) vm$do_operation(op, http_verb="POST")) + }, + + redeploy=function(id=NULL) + { + if(is.null(id)) + self$do_operation("redeploy", http_verb="POST") + else private$vm_map(id, function(vm) vm$do_operation("redeploy", http_verb="POST")) + }, + + mapped_vm_operation=function(..., id=NULL) + { + private$vm_map(id, function(vm) vm$do_operation(...)) + }, + + add_extension=function(publisher, type, version, settings=list(), + protected_settings=list(), key_vault_settings=list()) + { + name <- gsub("[[:punct:]]", "", type) + op <- file.path("extensions", name) + props <- list( + publisher=publisher, + type=type, + typeHandlerVersion=version, + autoUpgradeMinorVersion=TRUE, + settings=settings + ) + + if(!is_empty(protected_settings)) + props$protectedSettings <- protected_settings + if(!is_empty(key_vault_settings)) + props$protectedSettingsFromKeyVault <- key_vault_settings + + self$do_operation(op, body=list(properties=props), http_verb="PUT") + }, + + print=function(...) + { + cat("\n", sep="") + + osProf <- names(self$properties$virtualMachineProfile$osProfile) + os <- if(any(grepl("linux", osProf))) "Linux" else if(any(grepl("windows", osProf))) "Windows" else "" + + cat(" Operating system:", os, "\n") + cat(" Status:\n") + if(is_empty(self$status)) + cat(" \n") + else + { + status <- head(self$status) + row.names(status) <- paste0(" ", row.names(status)) + print(status) + if(nrow(self$status) > nrow(status)) + cat(" ...\n") + } + cat("---\n") + + exclude <- c("subscription", "resource_group", "type", "name", "status") + + cat(AzureRMR::format_public_fields(self, exclude=exclude)) + cat(AzureRMR::format_public_methods(self)) + invisible(NULL) + } +), + +private=list( + + make_vm_resource=function(params) + { + params$instanceId <- NULL + obj <- az_vm_resource$new(self$token, self$subscription, deployed_properties=params) + obj$nic_api_version <- "2018-10-01" + + # make type and name useful + obj$type <- self$type + obj$name <- file.path(self$name, "virtualMachines", basename(params$id)) + obj + }, + + vm_map=function(id, f) + { + vms <- self$list_instances() + if(!is.null(id)) + vms <- vms[as.character(id)] + lapply(vms, f) + } +)) diff --git a/R/az_vmss_template.R b/R/az_vmss_template.R new file mode 100644 index 0000000..7fcc988 --- /dev/null +++ b/R/az_vmss_template.R @@ -0,0 +1,213 @@ +#' Virtual machine scaleset (cluster) template class +#' +#' Class representing a virtual machine scaleset deployment template. This class keeps track of all resources that are created as part of deploying a scaleset, and exposes methods for managing them. +#' +#' @docType class +#' @section Methods: +#' The following methods are available, in addition to those provided by the [AzureRMR::az_template] class. +#' - `sync_vmss_status`: Check the status of the scaleset. +#' - `list_instances()`: Return a list of [az_vm_resource] objects, one for each VM instance in the scaleset. Note that if the scaleset has an autoscaler attached, the number of instances will vary depending on the load. +#' - `get_instance(id)`: Return a specific VM instance in the scaleset. +#' - `start(id=NULL, wait=FALSE)`: Start the scaleset. In this and the other methods listed here, `id` can be an optional character vector of instance IDs; if supplied, only carry out the operation for those instances. +#' - `restart(id=NULL, wait=FALSE)`: Restart the scaleset. +#' - `stop(deallocate=TRUE, id=NULL, wait=FALSE)`: Stop the scaleset. +#' - `get_public_ip_address()`: Get the public IP address of the scaleset (technically, of the load balancer). If the scaleset doesn't have a load balancer attached, returns NULL. +#' - `get_vm_public_ip_addresses(id=NULL, nic=1, config=1)`: Get the public IP addresses for the instances in the scaleset. Returns NULL if the instances are not publicly accessible. +#' - `get_vm_private_ip_addresses(id=NULL, nic=1, config=1)`: Get the private IP addresses for the instances in the scaleset. +#' - `run_deployed_command(command, parameters=NULL, script=NULL, id=NULL)`: Run a PowerShell command on the instances in the scaleset. +#' - `run_script(script, parameters=NULL, id=NULL)`: 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. +#' - `reimage(id=NULL, datadisks=FALSE)`: Reimage the instances in the scaleset. If `datadisks` is TRUE, reimage any attached data disks as well. +#' - `redeploy(id=NULL)`: Redeploy the instances in the scaleset. +#' - `mapped_vm_operation(..., id=NULL)`: Carry out an arbitrary operation on the instances in the scaleset. See the `do_operation` method of the [AzureRMR::az_resource] class for more details. +#' - `add_extension(publisher, type, version, settings=list(), protected_settings=list(), key_vault_settings=list())`: Add an extension to the scaleset. +#' - `do_vmss_operation(...)` Carry out an arbitrary operation on the scaleset resource (as opposed to the instances in the scaleset). +#' +#' @details +#' A virtual machine scaleset in Azure is actually a collection of resources, including any and all of the following. +#' - Network security group (Azure resource type `Microsoft.Network/networkSecurityGroups`) +#' - Virtual network (Azure resource type `Microsoft.Network/virtualNetworks`) +#' - Load balancer (Azure resource type `Microsoft.Network/loadBalancers`) +#' - Public IP address (Azure resource type `Microsoft.Network/publicIPAddresses`) +#' - Autoscaler (Azure resource type `Microsoft.Insights/autoscaleSettings`) +#' - The scaleset itself (Azure resource type `Microsoft.Compute/virtualMachineScaleSets`) +#' +#' By wrapping the deployment template used to create these resources, the `az_vmss_template` class allows managing them all as a single entity. +#' +#' @seealso +#' [AzureRMR::az_template], [create_vm_scaleset], [get_vm_scaleset], [delete_vm_scaleset] +#' +#' [VM scaleset API reference](https://docs.microsoft.com/en-us/rest/api/compute/virtualmachinescalesets) +#' +#' @examples +#' \dontrun{ +#' +#' sub <- AzureRMR::get_azure_login()$ +#' get_subscription("subscription_id") +#' +#' vmss <- sub$get_vm_scaleset("myscaleset") +#' +#' # start the VM +#' vmss$start() +#' +#' # run a shell command +#' vmss$run_script("ifconfig > /tmp/ifc.out") +#' +#' # get private IP addresses +#' vmss$get_vm_private_ip_addresses() +#' +#' # get the VM status +#' vmss$sync_vmss_status() +#' +#' } +#' @format An R6 object of class `az_vmss_template`, inheriting from `AzureRMR::az_template`. +#' @export +az_vmss_template <- R6::R6Class("az_vmss_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$vmss <- az_vmss_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_vmss_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("\n", sep="") + + osProf <- names(private$vmss$properties$virtualMachineProfile$osProfile) + os <- if(any(grepl("linux", osProf))) "Linux" else if(any(grepl("windows", osProf))) "Windows" else "" + exclusive <- self$properties$mode == "Complete" + + cat(" Operating system:", os, "\n") + cat(" Exclusive resource group:", exclusive, "\n") + cat(" Domain name:", self$dns_name, "\n") + cat(" Status:\n") + if(is_empty(private$vmss$status)) + cat(" \n") + else + { + status <- head(private$vmss$status) + row.names(status) <- paste0(" ", row.names(status)) + print(status) + if(nrow(private$vmss$status) > nrow(status)) + cat(" ...\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) + }, + + get_public_ip_address=function() + { + outputs <- unlist(self$properties$outputResources) + ip_id <- grep("publicIPAddresses/.+$", outputs, ignore.case=TRUE, value=TRUE) + if(is_empty(ip_id)) + return(NA_character_) + ip <- az_resource$new(self$token, self$subscription, id=ip_id)$properties$ipAddress + if(!is.null(ip)) + ip + else NA_character_ + } +), + +# propagate resource methods up to template +active=list( + + sync_vmss_status=function() + private$vmss$sync_vmss_status, + + list_instances=function() + private$vmss$list_instances, + + get_instance=function() + private$vmss$get_instance, + + start=function() + private$vmss$start, + + stop=function() + private$vmss$stop, + + restart=function() + private$vmss$restart, + + get_vm_public_ip_addresses=function() + private$vmss$get_vm_public_ip_addresses, + + get_vm_private_ip_addresses=function() + private$vmss$get_vm_private_ip_addresses, + + run_deployed_command=function() + private$vmss$run_deployed_command, + + run_script=function() + private$vmss$run_script, + + reimage=function() + private$vmss$reimage, + + redeploy=function() + private$vmss$redeploy, + + mapped_vm_operation=function() + private$vmss$mapped_vm_operation, + + add_extension=function() + private$vmss$add_extension, + + do_vmss_operation=function() + private$vmss$do_operation +), + +private=list( + vmss=NULL, + + reorder_for_delete=function() + { + is_type <- function(id, type) + { + grepl(type, id, fixed=TRUE) + } + new_order <- sapply(self$properties$outputResources, function(x) + { + id <- x$id + if(is_type(id, "Microsoft.Compute/virtualMachineScaleSets")) 1 + else if(is_type(id, "Microsoft.Compute/disks")) 2 + else if(is_type(id, "Microsoft.Insights/autoscaleSettings")) 3 + else if(is_type(id, "Microsoft.Network/loadBalancers")) 4 + else if(is_type(id, "Microsoft.Network/publicIPAddresses")) 5 + else if(is_type(id, "Microsoft.Network/virtualNetworks")) 6 + else if(is_type(id, "Microsoft.Network/networkSecurityGroups")) 7 + else 0 + }) + self$properties$outputResources <- self$properties$outputResources[order(new_order)] + } +)) diff --git a/R/build_json.R b/R/build_json.R new file mode 100644 index 0000000..84e34c6 --- /dev/null +++ b/R/build_json.R @@ -0,0 +1,218 @@ +#' Build template definition and parameters +#' +#' @param config An object of class `vm_config` or `vmss_config` representing a virtual machine or scaleset deployment. +#' @param name The VM or scaleset name. Will also be used for the domain name label, if a public IP address is included in the deployment. +#' @param login_user An object of class `user_config` representing the login details for the admin user account on the VM. +#' @param size The VM (instance) size. +#' @param ... Unused. +#' +#' @details +#' These are methods for the generics defined in the AzureRMR package. +#' +#' @seealso +#' [create_vm], [vm_config], [vmss_config] +#' +#' @examples +#' +#' vm <- ubuntu_18.04() +#' build_template_definition(vm) +#' build_template_parameters(vm, "myubuntuvm", +#' user_config("username", "~/.ssh/id_rsa.pub"), "Standard_DS3_v2") +#' +#' @rdname build_template +#' @export +build_template_definition.vm_config <- function(config, ...) +{ + tpl <- list( + `$schema`="http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + contentVersion="1.0.0.0", + parameters=add_template_parameters(config), + variables=add_template_variables(config), + resources=add_template_resources(config), + outputs=tpl_outputs_default + ) + + jsonlite::prettify(jsonlite::toJSON(tpl, auto_unbox=TRUE, null="null")) +} + + +#' @rdname build_template +#' @export +build_template_definition.vmss_config <- function(config, ...) +{ + tpl <- list( + `$schema`="http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + contentVersion="1.0.0.0", + parameters=add_template_parameters(config), + variables=add_template_variables(config), + resources=add_template_resources(config), + outputs=tpl_outputs_default + ) + + jsonlite::prettify(jsonlite::toJSON(tpl, auto_unbox=TRUE, null="null")) +} + + +#' @rdname build_template +#' @export +build_template_parameters.vm_config <- function(config, name, login_user, size, ...) +{ + add_parameters <- function(...) + { + new_params <- lapply(list(...), function(obj) list(value=obj)) + params <<- c(params, new_params) + } + + stopifnot(inherits(login_user, "user_config")) + + params <- list() + add_parameters(vmName=name, vmSize=size, adminUsername=login_user$user) + + if(config$keylogin && !is_empty(login_user$key)) + add_parameters(sshKeyData=login_user$key) + else add_parameters(adminPassword=login_user$pwd) + + if(inherits(config$image, "image_marketplace")) + add_parameters( + imagePublisher=config$image$publisher, + imageOffer=config$image$offer, + imageSku=config$image$sku, + imageVersion=config$image$version + ) + else add_parameters(imageId=config$image$id) + + # add datadisks to params + if(!is_empty(config$datadisks)) + { + # fixup datadisk LUNs and names + for(i in seq_along(config$datadisks)) + { + config$datadisks[[i]]$vm_spec$lun <- i - 1 + diskname <- config$datadisks[[i]]$vm_spec$name + if(!is.null(diskname)) + { + newdiskname <- paste(name, diskname, i, sep="_") + config$datadisks[[i]]$res_spec$name <- newdiskname + config$datadisks[[i]]$vm_spec$name <- newdiskname + } + } + + disk_res_spec <- lapply(config$datadisks, `[[`, "res_spec") + null <- sapply(disk_res_spec, is.null) + + add_parameters( + dataDisks=lapply(config$datadisks, `[[`, "vm_spec"), + dataDiskResources=disk_res_spec[!null] + ) + } + + jsonlite::prettify(jsonlite::toJSON(params, auto_unbox=TRUE, null="null")) +} + + +#' @param instances For `vmss_config`, the number of (initial) instances in the VM scaleset. +#' @rdname build_template +#' @export +build_template_parameters.vmss_config <- function(config, name, login_user, size, instances, ...) +{ + add_parameters <- function(...) + { + new_params <- lapply(list(...), function(obj) list(value=obj)) + params <<- c(params, new_params) + } + + stopifnot(inherits(login_user, "user_config")) + + params <- list() + add_parameters(vmName=name, vmSize=size, instanceCount=instances, adminUsername=login_user$user) + + if(config$options$keylogin && !is_empty(login_user$key)) + add_parameters(sshKeyData=login_user$key) + else add_parameters(adminPassword=login_user$pwd) + + if(inherits(config$image, "image_marketplace")) + add_parameters( + imagePublisher=config$image$publisher, + imageOffer=config$image$offer, + imageSku=config$image$sku, + imageVersion=config$image$version + ) + else add_parameters(imageId=config$image$id) + + do.call(add_parameters, config$options$params) + + jsonlite::prettify(jsonlite::toJSON(params, auto_unbox=TRUE, null="null")) +} + + +add_template_parameters <- function(config, ...) +{ + UseMethod("add_template_parameters") +} + + +add_template_variables <- function(config, ...) +{ + UseMethod("add_template_variables") +} + + +add_template_variables.character <- function(config, type, ...) +{ + # assume this is a resource ID + resname <- basename(config) + varnames <- paste0(type, c("Name", "Id")) + structure(list(resname, config), names=varnames) +} + + +add_template_variables.az_resource <- function(config, type, ...) +{ + varnames <- paste0(type, c("Name", "Id")) + vars <- list(config$name, config$id) + + # a bit hackish, should fully objectify + if(type == "vnet") # if we have a vnet, extract the 1st subnet name + { + subnet <- config$properties$subnets[[1]]$name + subnet_id <- "[concat(variables('vnetId'), '/subnets/', variables('subnet'))]" + varnames <- c(varnames, "subnet", "subnetId") + structure(c(vars, subnet, subnet_id), names=varnames) + } + else if(type == "lb") # if we have a load balancer, extract component names + { + frontend <- config$properties$frontendIPConfigurations[[1]]$name + backend <- config$properties$backendAddressPools[[1]]$name + frontend_id <- "[concat(variables('lbId'), '/frontendIPConfigurations/', variables('lbFrontendName'))]" + backend_id <- "[concat(variables('lbId'), '/backendAddressPools/', variables('lbBackendName'))]" + varnames <- c(varnames, "lbFrontendName", "lbBackendName", "lbFrontendId", "lbBackendId") + structure(c(vars, frontend, backend, frontend_id, backend_id), names=varnames) + } + else structure(vars, names=varnames) +} + + +add_template_variables.NULL <- function(config, ...) +{ + NULL +} + + +add_template_resources <- function(config, ...) +{ + UseMethod("add_template_resources") +} + + +build_resource_fields <- function(config) +{ + UseMethod("build_resource_fields") +} + + +build_resource_fields.list <- function(config, ...) +{ + unclass(config) +} + + diff --git a/R/get_paged_list.R b/R/get_paged_list.R new file mode 100644 index 0000000..ffde4c2 --- /dev/null +++ b/R/get_paged_list.R @@ -0,0 +1,12 @@ +# from AzureRMR +get_paged_list <- function(lst, token, next_link_name="nextLink", value_name="value") +{ + res <- lst[[value_name]] + while(!is_empty(lst[[next_link_name]])) + { + lst <- call_azure_url(token, lst[[next_link_name]]) + res <- c(res, lst[[value_name]]) + } + res +} + diff --git a/R/ip_config.R b/R/ip_config.R new file mode 100644 index 0000000..025273f --- /dev/null +++ b/R/ip_config.R @@ -0,0 +1,50 @@ +#' Public IP address configuration +#' +#' @param type The SKU of the IP address resource: "basic" or "standard". If NULL (the default), this will be determined based on the VM's configuration. +#' @param dynamic Whether the IP address should be dynamically or statically allocated. Note that the standard SKU only supports standard allocation. If NULL (the default) this will be determined based on the VM's configuration. +#' @param ipv6 Whether to create an IPv6 address. The default is IPv4. +#' @param domain_name The domain name label to associate with the address. +#' @param ... Other named arguments that will be treated as resource properties. +#' +#' @seealso +#' [create_vm], [vm_config], [vmss_config] +#' @export +ip_config <- function(type=NULL, dynamic=NULL, ipv6=FALSE, domain_name="[parameters('vmName')]", ...) +{ + # structure(list(properties=props, sku=list(name=type)), class="ip_config") + props <- list( + type=type, + dynamic=dynamic, + ipv6=ipv6, + domain_name=domain_name, + other=list(...) + ) + structure(props, class="ip_config") +} + + +build_resource_fields.ip_config <- function(config, ...) +{ + alloc <- if(config$dynamic) "dynamic" else "static" + version <- if(config$ipv6) "IPv6" else "IPv4" + props <- c( + list( + publicIPAllocationMethod=alloc, + publicIPAddressVersion=version + ), + config$other) + if(!is.null(config$domain_name)) + props$dnsSettings$domainNameLabel <- config$domain_name + + sku <- list(name=config$type) + utils::modifyList(ip_default, list(properties=props, sku=sku)) +} + + +add_template_variables.ip_config <- function(config, ...) +{ + name <- "[concat(parameters('vmName'), '-ip')]" + id <- "[resourceId('Microsoft.Network/publicIPAddresses', variables('ipName'))]" + ref <- "[concat('Microsoft.Network/publicIPAddresses/', variables('ipName'))]" + list(ipName=name, ipId=id, ipRef=ref) +} diff --git a/R/is_vm.R b/R/is_vm.R new file mode 100644 index 0000000..c4d863e --- /dev/null +++ b/R/is_vm.R @@ -0,0 +1,49 @@ +#' Is an object an Azure VM +#' +#' @param object an R object. +#' +#' @return +#' `is_vm` and `is_vm_template` return TRUE for an object representing a virtual machine deployment (which will include other resources besides the VM itself). +#' +#' `is_vm_resource` returns TRUE for an object representing the specific VM resource. +#' +#' `is_vm_scaleset` and `is_vm_scaleset_template` return TRUE for an object representing a VM scaleset deployment. +#' +#' `is_vm_scaleset_resource` returns TRUE for an object representing the specific VM scaleset resource. +#' +#' @seealso +#' [create_vm], [create_vm_scaleset], [az_vm_template], [az_vm_resource], [az_vmss_template], [az_vmss_resource] +#' @export +is_vm <- function(object) +{ + R6::is.R6(object) && inherits(object, "az_vm_template") +} + +#' @rdname is_vm +#' @export +is_vm_template <- is_vm + +#' @rdname is_vm +#' @export +is_vm_resource <- function(object) +{ + R6::is.R6(object) && inherits(object, "az_vm_resource") +} + +#' @rdname is_vm +#' @export +is_vm_scaleset <- function(object) +{ + R6::is.R6(object) && inherits(object, "az_vmss_template") +} + +#' @rdname is_vm +#' @export +is_vm_scaleset_template <- is_vm_scaleset + +#' @rdname is_vm +#' @export +is_vm_scaleset_resource <- function(object) +{ + R6::is.R6(object) && inherits(object, "az_vmss_resource") +} diff --git a/R/lb_config.R b/R/lb_config.R new file mode 100644 index 0000000..14d220b --- /dev/null +++ b/R/lb_config.R @@ -0,0 +1,203 @@ +#' Load balancer configuration +#' +#' @param type The SKU of the load balancer resource: "basic" or "standard". If NULL (the default), this will be determined based on the VM scaleset's configuration. Note that the load balancer SKU must be the same as that of its public IP address. +#' @param rules A list of load balancer rules, each obtained via a call to `lb_rule`. +#' @param probes A list of health checking probes, each obtained via a call to `lb_probe`. There must be a probe corresponding to each rule. +#' @param ... Other named arguments that will be treated as resource properties. +#' @param port For `lb_probe`, the port to probe. +#' @param interval For `lb_probe`, the time interval between probes in seconds. +#' @param fail_on For `lb_probe`, the probe health check will fail after this many non-responses. +#' @param protocol For `lb_probe` and `lb_rule`, the protocol: either "Tcp" or "Ip". +#' @param name For `lb_rule`, a name for the load balancing rule. +#' @param frontend_port,backend_port For `lb_rule`, the ports for this rule. +#' @param timeout The timeout interval for the rule. The default is 5 minutes. +#' @param floating_ip Whether to use floating IP addresses (direct server return). Only needed for specific scenarios, and when the frontend and backend ports don't match. +#' @param probe_name The name of the corresponding health check probe. +#' +#' @seealso +#' [create_vm_scaleset], [vmss_config], [lb_rules] for some predefined load balancing rules and probes +#' @export +lb_config <- function(type=NULL, rules=list(), probes=list(), ...) +{ + rule_probe_names <- sapply(rules, function(x) x$properties$probe$id) + probe_names <- sapply(probes, `[[`, "name") + + # basic checking + for(r in rule_probe_names) + { + found <- FALSE + for(p in probe_names) + { + found <- grepl(p, r, fixed=TRUE) + if(found) break + } + if(!found) + stop("Rule with no matching probe: ", r, call.=FALSE) + } + + props <- list( + type=type, + rules=rules, + probes=probes, + other=list(...) + ) + structure(props, class="lb_config") +} + + +build_resource_fields.lb_config <- function(config, ...) +{ + props <- c( + list( + loadBalancingRules=lapply(config$rules, unclass), + probes=lapply(config$probes, unclass) + ), + config$other + ) + sku <- list(name=config$type) + utils::modifyList(lb_default, list(properties=props, sku=sku)) +} + + +add_template_variables.lb_config <- function(config, ...) +{ + name <- "[concat(parameters('vmName'), '-lb')]" + id <- "[resourceId('Microsoft.Network/loadBalancers', variables('lbName'))]" + ref <- "[concat('Microsoft.Network/loadBalancers/', variables('lbName'))]" + frontend <- "frontend" + backend <- "backend" + frontend_id <- "[concat(variables('lbId'), '/frontendIPConfigurations/', variables('lbFrontendName'))]" + backend_id <- "[concat(variables('lbId'), '/backendAddressPools/', variables('lbBackendName'))]" + list( + lbName=name, + lbId=id, + lbRef=ref, + lbFrontendName=frontend, + lbBackendName=backend, + lbFrontendId=frontend_id, + lbBackendId=backend_id + ) +} + + +#' @rdname lb_config +#' @export +lb_probe <- function(name, port, interval=5, fail_on=2, protocol="Tcp") +{ + props <- list( + port=port, + intervalInSeconds=interval, + numberOfProbes=fail_on, + protocol=protocol + ) + + structure(list(name=name, properties=props), class="lb_probe") +} + + +#' @rdname lb_config +#' @export +lb_rule <- function(name, frontend_port, backend_port=frontend_port, protocol="Tcp", timeout=5, + floating_ip=FALSE, probe_name) +{ + frontend_id <- "[variables('lbFrontendId')]" + backend_id <- "[variables('lbBackendId')]" + probe_id <- sprintf("[concat(variables('lbId'), '/probes/%s')]", probe_name) + + props <- list( + frontendIpConfiguration=list(id=frontend_id), + backendAddressPool=list(id=backend_id), + protocol=protocol, + frontendPort=frontend_port, + backendPort=backend_port, + enableFloatingIp=floating_ip, + idleTimeoutInMinutes=timeout, + probe=list(id=probe_id) + ) + + structure(list(name=name, properties=props), class="lb_rule") +} + + +#' Load balancing rules +#' +#' @format +#' Objects of class `lb_rule` and `lb_probe`. +#' @details +#' Some predefined load balancing objects, for commonly used ports. Each load balancing rule comes with its own health probe. +#' - HTTP: TCP port 80 +#' - HTTPS: TCP port 443 +#' - JupyterHub: TCP port 8000 +#' - RDP: TCP port 3389 +#' - RStudio Server: TCP port 8787 +#' - SSH: TCP port 22 +#' - SQL Server: TCP port 1433 +#' - SQL Server browser service: TCP port 1434 +#' @docType data +#' @seealso +#' [lb_config] +#' @rdname lb_rules +#' @aliases lb_rules +#' @export +lb_rule_ssh <- lb_rule("lb-ssh", 22, 22, probe_name="probe-ssh") + +#' @rdname lb_rules +#' @export +lb_rule_http <- lb_rule("lb-http", 80, 80, probe_name="probe-http") + +#' @rdname lb_rules +#' @export +lb_rule_https <- lb_rule("lb-https", 443, 443, probe_name="probe-https") + +#' @rdname lb_rules +#' @export +lb_rule_rdp <- lb_rule("lb-rdp", 3389, 3389, probe_name="probe-rdp") + +#' @rdname lb_rules +#' @export +lb_rule_jupyter <- lb_rule("lb-jupyter", 8000, 8000, probe_name="probe-jupyter") + +#' @rdname lb_rules +#' @export +lb_rule_rstudio <- lb_rule("lb-rstudio", 8787, 8787, probe_name="probe-rstudio") + +#' @rdname lb_rules +#' @export +lb_rule_mssql <- lb_rule("lb-mssql", 1433, 1433, probe_name="probe-mssql") + +#' @rdname lb_rules +#' @export +lb_rule_mssql_browser <- lb_rule("lb-mssql-browser", 1434, 1434, probe_name="probe-mssql-browser") + +#' @rdname lb_rules +#' @export +lb_probe_ssh <- lb_probe("probe-ssh", 22) + +#' @rdname lb_rules +#' @export +lb_probe_http <- lb_probe("probe-http", 80) + +#' @rdname lb_rules +#' @export +lb_probe_https <- lb_probe("probe-https", 443) + +#' @rdname lb_rules +#' @export +lb_probe_rdp <- lb_probe("probe-rdp", 3389) + +#' @rdname lb_rules +#' @export +lb_probe_jupyter <- lb_probe("probe-jupyter", 8000) + +#' @rdname lb_rules +#' @export +lb_probe_rstudio <- lb_probe("probe-rstudio", 8787) + +#' @rdname lb_rules +#' @export +lb_probe_mssql <- lb_probe("probe-mssql", 1433) + +#' @rdname lb_rules +#' @export +lb_probe_mssql_browser <- lb_probe("probe-mssql-browser", 1434) + diff --git a/R/load_tpls.R b/R/load_tpls.R new file mode 100644 index 0000000..79241aa --- /dev/null +++ b/R/load_tpls.R @@ -0,0 +1,8 @@ +lapply(dir("inst/tpl", pattern="\\.json$"), function(f) +{ + objname <- sub("\\.json$", "", f) + obj <- jsonlite::fromJSON(file.path("inst/tpl", f), simplifyVector=FALSE) + + assign(objname, obj, parent.env(environment())) +}) + diff --git a/R/nic_config.R b/R/nic_config.R new file mode 100644 index 0000000..f28e7d5 --- /dev/null +++ b/R/nic_config.R @@ -0,0 +1,59 @@ +#' Network interface configuration +#' +#' @param nic_ip For `nic_config`, a list of IP configuration objects, each obtained via a call to `nic_ip_config`. +#' @param name For `nic_ip_config`, the name of the IP configuration. +#' @param private_alloc For `nic_ip_config`, the allocation method for a private IP address. Can be "dynamic" or "static". +#' @param subnet For `nic_ip_config`, the subnet to associate with this private IP address. +#' @param public_address For `nic_ip_config`, the public IP address. Defaults to the public IP address created or used as part of this VM deployment. Ignored if the deployment does not include a public address. +#' @param ... Other named arguments that will be treated as resource properties. +#' +#' @seealso +#' [create_vm], [vm_config] +#' @export +nic_config <- function(nic_ip=list(nic_ip_config()), ...) +{ + # unique-ify ip config names + if(length(nic_ip) > 1) + { + ip_names <- make.unique(sapply(nic_ip, `[[`, "name")) + ip_names[1] <- paste0(ip_names[1], "0") + for(i in seq_along(nic_ip)) + nic_ip[[i]]$name <- ip_names[i] + } + + props <- list(ipConfigurations=nic_ip, ...) + structure(list(properties=props), class="nic_config") +} + + +build_resource_fields.nic_config <- function(config) +{ + config$properties$ipConfigurations <- lapply(config$properties$ipConfigurations, unclass) + utils::modifyList(nic_default, config) +} + + +add_template_variables.nic_config <- function(config, ...) +{ + name <- "[concat(parameters('vmName'), '-nic')]" + id <- "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]" + ref <- "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]" + list(nicName=name, nicId=id, nicRef=ref) +} + + +#' @rdname nic_config +#' @export +nic_ip_config <- function(name="ipconfig", private_alloc="dynamic", subnet="[variables('subnetId')]", + public_address="[variables('ipId')]", ...) +{ + props <- list( + privateIPAllocationMethod=private_alloc, + subnet=list(id=subnet), + publicIPAddress=list(id=public_address), + ... + ) + structure(list(name=name, properties=props), class="nic_ip_config") +} + + diff --git a/R/nsg_config.R b/R/nsg_config.R new file mode 100644 index 0000000..e337e29 --- /dev/null +++ b/R/nsg_config.R @@ -0,0 +1,127 @@ +#' Network security group configuration +#' +#' @param rules for `nsg_config`, a list of security rule objects, each obtained via a call to `nsg_rule`. +#' @param dest_port,dest_addr,dest_asgs For `nsg_rule`, the destination port, address range, and application security groups for a rule. +#' @param source_port,source_addr,source_asgs For `nsg_rule`, the source port, address range, and application security groups for a rule. +#' @param ... Other named arguments that will be treated as resource properties. +#' @param name For `nsg_rule`, a name for the rule. +#' @param access For `nsg_rule`, the action to take: "allow" or "deny". +#' @param direction For `nsg_rule`, the direction of traffic: "inbound" or "outbound". +#' @param protocol For `nsg_rule`, the network protocol: either "Tcp" or "Udp". +#' @param priority For `nsg_rule`, the rule priority. If NULL, this will be set automatically by AzureVM. +#' +#' @seealso +#' [create_vm], [vm_config], [vmss_config], [nsg_rules] for some predefined security rules +#' @export +nsg_config <- function(rules=list(), ...) +{ + stopifnot(is.list(rules)) + props <- list(securityRules=rules, ...) + structure(list(properties=props), class="nsg_config") +} + + +build_resource_fields.nsg_config <- function(config, ...) +{ + for(i in seq_along(config$properties$securityRules)) + { + # fixup nsg security rule priorities + if(is_empty(config$properties$securityRules[[i]]$properties$priority)) + config$properties$securityRules[[i]]$properties$priority <- 1000 + 10 * i + + config$properties$securityRules[[i]] <- unclass(config$properties$securityRules[[i]]) + } + + utils::modifyList(nsg_default, config) +} + + +add_template_variables.nsg_config <- function(config, ...) +{ + name <- "[concat(parameters('vmName'), '-nsg')]" + id <- "[resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))]" + ref <- "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]" + list(nsgName=name, nsgId=id, nsgRef=ref) +} + + +#' @rdname nsg_config +#' @export +nsg_rule <- function(name, dest_port="*", dest_addr="*", dest_asgs=NULL, + source_port="*", source_addr="*", source_asgs=NULL, + access="allow", direction="inbound", + protocol="Tcp", priority=NULL) +{ + if(is_empty(dest_asgs)) + dest_asgs <- logical(0) + if(is_empty(source_asgs)) + source_asgs <- logical(0) + + properties <- list( + protocol=protocol, + access=access, + direction=direction, + sourceApplicationSecurityGroups=source_asgs, + destinationApplicationSecurityGroups=dest_asgs, + sourceAddressPrefix=source_addr, + sourcePortRange=as.character(source_port), + destinationAddressPrefix=dest_addr, + destinationPortRange=as.character(dest_port) + ) + + if(!is_empty(priority)) + properties$priority <- priority + + structure(list(name=name, properties=properties), class="nsg_rule") +} + + +#' Network security rules +#' +#' @format +#' Objects of class `nsg_rule`. +#' @details +#' Some predefined network security rule objects, to unblock commonly used ports. +#' - HTTP: TCP port 80 +#' - HTTPS: TCP port 443 +#' - JupyterHub: TCP port 8000 +#' - RDP: TCP port 3389 +#' - RStudio Server: TCP port 8787 +#' - SSH: TCP port 22 +#' - SQL Server: TCP port 1433 +#' - SQL Server browser service: TCP port 1434 +#' @docType data +#' @seealso +#' [nsg_config] +#' @rdname nsg_rules +#' @aliases nsg_rules +#' @export +nsg_rule_allow_ssh <- nsg_rule("Allow-ssh", 22) + +#' @rdname nsg_rules +#' @export +nsg_rule_allow_http <- nsg_rule("Allow-http", 80) + +#' @rdname nsg_rules +#' @export +nsg_rule_allow_https <- nsg_rule("Allow-https", 443) + +#' @rdname nsg_rules +#' @export +nsg_rule_allow_rdp <- nsg_rule("Allow-rdp", 3389) + +#' @rdname nsg_rules +#' @export +nsg_rule_allow_jupyter <- nsg_rule("Allow-jupyter", 8000) + +#' @rdname nsg_rules +#' @export +nsg_rule_allow_rstudio <- nsg_rule("Allow-rstudio", 8787) + +#' @rdname nsg_rules +#' @export +nsg_rule_allow_mssql <- nsg_rule("Allow-mssql", 1433) + +#' @rdname nsg_rules +#' @export +nsg_rule_allow_mssql_browser <- nsg_rule("Allow-mssql-browser", 1434) diff --git a/R/params.R b/R/params.R deleted file mode 100644 index cf47ba0..0000000 --- a/R/params.R +++ /dev/null @@ -1,68 +0,0 @@ -param_mappings <- list() - -param_mappings$win2016_dsvm <- c( - username="adminUsername", - passkey="adminPassword", - name="vmName", - size="vmSize" -) - -param_mappings$win2016_dsvm_cl_ext <- c( - username="adminUsername", - passkey="adminPassword", - name="vmName", - size="vmSize", - clust_size="numberOfInstances", - ext_file_uris="fileUris", - inst_command="commandToExecute" -) - -param_mappings$ubuntu_dsvm <- c( - username="adminUsername", - passkey="adminPassword", - name="vmName", - size="vmSize" -) - -param_mappings$ubuntu_dsvm_key <- c( - username="adminUsername", - passkey="sshKeyData", - name="vmName", - size="vmSize" -) - -param_mappings$ubuntu_dsvm_ext <- c( - username="adminUsername", - passkey="adminPassword", - name="vmName", - size="vmSize", - ext_file_uris="fileUris", - inst_command="commandToExecute", - command_parm="commandParameter" -) - -param_mappings$ubuntu_dsvm_cl <- c( - username="adminUsername", - passkey="adminPassword", - name="vmName", - size="vmSize", - clust_size="numberOfInstances" -) - -param_mappings$ubuntu_dsvm_cl_key <- c( - username="adminUsername", - passkey="sshKeyData", - name="vmName", - size="vmSize", - clust_size="numberOfInstances" -) - -param_mappings$ubuntu_dsvm_cl_ext <- c( - username="adminUsername", - passkey="adminPassword", - name="vmName", - size="vmSize", - clust_size="numberOfInstances", - ext_file_uris="fileUris", - inst_command="commandToExecute" -) diff --git a/R/vm_config.R b/R/vm_config.R new file mode 100644 index 0000000..f7edeed --- /dev/null +++ b/R/vm_config.R @@ -0,0 +1,253 @@ +#' VM configuration functions +#' +#' @param image For `vm_config`, the VM image to deploy. This should be an object of class `image_config`, created by the function of the same name. +#' @param keylogin Boolean: whether to use an SSH public key to login (TRUE) or a password (FALSE). Note that Windows does not support SSH key logins. +#' @param managed Whether to provide a managed system identity for the VM. +#' @param datadisks The data disks to attach to the VM. Specify this as either a vector of numeric disk sizes in GB, or a list of `datadisk_config` objects for more control over the specification. +#' @param nsg The network security group for the VM. Can be a call to `nsg_config` to create a new NSG; an AzureRMR resource object or resource ID to reuse an existing NSG; or NULL to not use an NSG (not recommended). +#' @param ip The public IP address for the VM. Can be a call to `ip_config` to create a new IP address; an AzureRMR resource object or resource ID to reuse an existing address resource; or NULL if the VM should not be accessible from outside its subnet. +#' @param vnet The virtual network for the VM. Can be a call to `vnet_config` to create a new virtual network, or an AzureRMR resource object or resource ID to reuse an existing virtual network. Note that by default, AzureVM will associate the NSG with the virtual network/subnet, not with the VM's network interface. +#' @param nic The network interface for the VM. Can be a call to `nic_config` to create a new interface, or an AzureRMR resource object or resource ID to reuse an existing interface. +#' @param other_resources An optional list of other resources to include in the deployment. +#' @param variables An optional named list of variables to add to the template. +#' @param ... For the specific VM configurations, other customisation arguments to be passed to `vm_config`. For `vm_config`, named arguments that will be folded into the VM resource definition in the template. +#' +#' @details +#' These functions are for specifying the details of a new virtual machine deployment: the VM image and related options, along with the Azure resources that the VM may need. These include the datadisks, network security group, public IP address (if the VM is to be accessible from outside its subnet), virtual network, and network interface. +#' +#' Each resource can be specified in a number of ways: +#' - To _create_ a new resource as part of the deployment, call the corresponding `*_config` function. +#' - To use an _existing_ resource, supply either an `AzureRMR::az_resource` object (recommended) or a string containing the resource ID. +#' - If the resource is not needed, specify it as NULL. +#' - For the `other_resources` argument, supply a list of resources, each of which should be a list of resource fields (name, type, properties, sku, etc). +#' +#' The `vm_config` function is the base configuration function, and the others call it to create VMs with specific operating systems and other image details. +#' - `ubuntu_dsvm`: Data Science Virtual Machine, based on Ubuntu 16.04 +#' - `windows_dsvm`: Data Science Virtual Machine, based on Windows Server 2016 +#' - `ubuntu_16.04`, `ubuntu_18.04`: Ubuntu +#' - `windows_2016`, `windows_2019`: Windows Server Datacenter edition +#' - `rhel_7.6`, `rhel_8`: Red Hat Enterprise Linux +#' - `debian_9_backports`: Debian +#' +#' @return +#' An object of S3 class `vm_config`, that can be used by the `create_vm` method. +#' +#' @seealso +#' [image_config], [user_config], [datadisk_config] for options relating to the VM resource itself +#' +#' [nsg_config], [ip_config], [vnet_config], [nic_config] for other resource configs +#' +#' [vmss_config] for configuring a virtual machine scaleset +#' +#' [create_vm] +#' +#' @examples +#' +#' # basic Linux (Ubuntu) and Windows configs +#' ubuntu_18.04() +#' windows_2019() +#' +#' # Windows DSVM with 500GB data disk, no public IP address +#' windows_dsvm(datadisks=500, ip=NULL) +#' +#' # RHEL VM exposing ports 80 (HTTP) and 443 (HTTPS) +#' rhel_8(nsg=nsg_config(nsg_rule_allow_http, nsg_rule_allow_https)) +#' +#' # exposing no ports externally +#' rhel_8(nsg=nsg_config(list())) +#' +#' # deploying an extra resource: storage account +#' ubuntu_18.04( +#' variables=list(storName="[concat(variables('vmName'), 'stor')]"), +#' other_resources=list( +#' list( +#' type="Microsoft.Storage/storageAccounts", +#' name="[variables('storName')]", +#' apiVersion="2018-07-01", +#' location="[variables('location')]", +#' properties=list(supportsHttpsTrafficOnly=TRUE), +#' sku=list(name="Standard_LRS"), +#' kind="Storage" +#' ) +#' ) +#' ) +#' +#' ## custom VM configuration: Windows 10 Pro 1903 with data disks +#' ## this assumes you have a valid Win10 desktop license +#' user <- user_config("myname", password="Use-strong-passwords!") +#' image <- image_config( +#' publisher="MicrosoftWindowsDesktop", +#' offer="Windows-10", +#' sku="19h1-pro" +#' ) +#' datadisks <- list( +#' datadisk_config(250, type="Premium_LRS"), +#' datadisk_config(1000, type="Standard_LRS") +#' ) +#' nsg <- nsg_config( +#' list(nsg_rule_allow_rdp) +#' ) +#' vm_config( +#' image=image, +#' keylogin=FALSE, +#' datadisks=datadisks, +#' nsg=nsg, +#' properties=list(licenseType="Windows_Client") +#' ) +#' +#' +#' \dontrun{ +#' +#' # reusing existing resources: placing multiple VMs in one vnet/subnet +#' rg <- AzureRMR::get_azure_login()$ +#' get_subscription("sub_id")$ +#' get_resource_group("rgname") +#' +#' vnet <- rg$get_resource(type="Microsoft.Network/virtualNetworks", name="myvnet") +#' +#' # by default, the NSG is associated with the subnet, so we don't need a new NSG either +#' vmconfig1 <- ubuntu_18.04(vnet=vnet, nsg=NULL) +#' vmconfig2 <- debian_9_backports(vnet=vnet, nsg=NULL) +#' vmconfig3 <- windows_2019(vnet=vnet, nsg=NULL) +#' +#' } +#' @export +vm_config <- function(image, keylogin, managed=TRUE, + datadisks=numeric(0), + nsg=nsg_config(), + ip=ip_config(), + vnet=vnet_config(), + nic=nic_config(), + other_resources=list(), + variables=list(), + ...) +{ + if(is.numeric(datadisks)) + datadisks <- lapply(datadisks, datadisk_config) + + stopifnot(inherits(image, "image_config")) + stopifnot(is.list(datadisks) && all(sapply(datadisks, inherits, "datadisk_config"))) + + ip <- vm_fixup_ip(ip) + + obj <- list( + image=image, + keylogin=keylogin, + managed=managed, + datadisks=datadisks, + nsg=nsg, + ip=ip, + vnet=vnet, + nic=nic, + other=other_resources, + variables=variables, + vm_fields=list(...) + ) + structure(obj, class="vm_config") +} + + +vm_fixup_ip <- function(ip) +{ + # don't try to fix IP if not created here + if(is.null(ip) || !inherits(ip, "ip_config")) + return(ip) + + # default for a regular VM: sku=basic, allocation=dynamic + if(is.null(ip$type)) + ip$type <- "basic" + if(is.null(ip$dynamic)) + ip$dynamic <- tolower(ip$type) == "basic" + + # check consistency + if(tolower(ip$type) == "standard" && ip$dynamic) + stop("Standard IP address type does not support dynamic allocation", call.=FALSE) + + ip +} + + +#' @rdname vm_config +#' @export +ubuntu_dsvm <- function(keylogin=TRUE, managed=TRUE, datadisks=numeric(0), + nsg=nsg_config(list(nsg_rule_allow_ssh, nsg_rule_allow_jupyter, nsg_rule_allow_rstudio)), + ...) +{ + if(is.numeric(datadisks)) + datadisks <- lapply(datadisks, datadisk_config) + disk0 <- datadisk_config(NULL, NULL, "fromImage", "Premium_LRS") + vm_config(image_config("microsoft-dsvm", "linux-data-science-vm-ubuntu", "linuxdsvmubuntu"), + keylogin=keylogin, managed=managed, datadisks=c(list(disk0), datadisks), nsg=nsg, ...) +} + +#' @rdname vm_config +#' @export +windows_dsvm <- function(keylogin=FALSE, managed=TRUE, datadisks=numeric(0), + nsg=nsg_config(list(nsg_rule_allow_rdp)), ...) +{ + vm_config(image_config("microsoft-dsvm", "dsvm-windows", "server-2016"), + keylogin=FALSE, managed=managed, datadisks=datadisks, nsg=nsg, ...) +} + +#' @rdname vm_config +#' @export +ubuntu_16.04 <- function(keylogin=TRUE, managed=TRUE, datadisks=numeric(0), + nsg=nsg_config(list(nsg_rule_allow_ssh)), ...) +{ + vm_config(image_config("Canonical", "UbuntuServer", "16.04-LTS"), + keylogin=keylogin, managed=managed, datadisks=datadisks, nsg=nsg, ...) +} + +#' @rdname vm_config +#' @export +ubuntu_18.04 <- function(keylogin=TRUE, managed=TRUE, datadisks=numeric(0), + nsg=nsg_config(list(nsg_rule_allow_ssh)), ...) +{ + vm_config(image_config("Canonical", "UbuntuServer", "18.04-LTS"), + keylogin=keylogin, managed=managed, datadisks=datadisks, nsg=nsg, ...) +} + +#' @rdname vm_config +#' @export +windows_2016 <- function(keylogin=FALSE, managed=TRUE, datadisks=numeric(0), + nsg=nsg_config(list(nsg_rule_allow_rdp)), ...) +{ + vm_config(image_config("MicrosoftWindowsServer", "WindowsServer", "2016-Datacenter"), + keylogin=FALSE, managed=managed, datadisks=datadisks, nsg=nsg, ...) +} + +#' @rdname vm_config +#' @export +windows_2019 <- function(keylogin=FALSE, managed=TRUE, datadisks=numeric(0), + nsg=nsg_config(list(nsg_rule_allow_rdp)), ...) +{ + vm_config(image_config("MicrosoftWindowsServer", "WindowsServer", "2019-Datacenter"), + keylogin=FALSE, managed=managed, datadisks=datadisks, nsg=nsg, ...) +} + +#' @rdname vm_config +#' @export +rhel_7.6 <- function(keylogin=TRUE, managed=TRUE, datadisks=numeric(0), + nsg=nsg_config(list(nsg_rule_allow_ssh)), ...) +{ + vm_config(image_config("RedHat", "RHEL", "7-RAW"), + keylogin=keylogin, managed=managed, datadisks=datadisks, nsg=nsg, ...) +} + +#' @rdname vm_config +#' @export +rhel_8 <- function(keylogin=TRUE, managed=TRUE, datadisks=numeric(0), + nsg=nsg_config(list(nsg_rule_allow_ssh)), ...) +{ + vm_config(image_config("RedHat", "RHEL", "8"), + keylogin=keylogin, managed=managed, datadisks=datadisks, nsg=nsg, ...) +} + +#' @rdname vm_config +#' @export +debian_9_backports <- function(keylogin=TRUE, managed=TRUE, datadisks=numeric(0), + nsg=nsg_config(list(nsg_rule_allow_ssh)), ...) +{ + vm_config(image_config("Credativ", "Debian", "9-backports"), + keylogin=keylogin, managed=managed, datadisks=datadisks, nsg=nsg, ...) +} diff --git a/R/vm_resource_config.R b/R/vm_resource_config.R new file mode 100644 index 0000000..50ca06b --- /dev/null +++ b/R/vm_resource_config.R @@ -0,0 +1,79 @@ +#' Resource configuration functions for a virtual machine deployment +#' +#' @param username For `user_config`, the name for the admin user account. +#' @param sshkey For `user_config`, string containing an SSH public key. Can be the key itself, or the name of the public key file. +#' @param password For `user_config`, the admin password. Supply either `sshkey` or `password`, but not both; also, note that Windows does not support SSH logins. +#' @param size For `datadisk_config`, the size of the data disk in GB. St this to NULL for a disk that will be created from an image. +#' @param name For `datadisk_config`, the disk name. Duplicate names will automatically be disambiguated prior to VM deployment. +#' @param create For `datadisk_config`, the creation method. Can be "empty" (the default) to create a blank disk, or "fromImage" to use an image. +#' @param type For `datadisk_config`, the disk type (SKU). Can be "Standard_LRS", "StandardSSD_LRS" (the default), "Premium_LRS" or "UltraSSD_LRS". Of these, "Standard_LRS" uses hard disks and the others use SSDs as the underlying hardware. +#' @param write_accelerator For `datadisk_config`, whether the disk should have write acceleration enabled. +#' @param publisher,offer,sku,version For `image_config`, the details for a marketplace image. +#' @param id For `image_config`, the resource ID for a disk to use as a custom image. +#' +#' @rdname vm_resource_config +#' @export +user_config <- function(username, sshkey=NULL, password=NULL) +{ + pwd <- is.character(password) + key <- is.character(sshkey) + if(!pwd && !key) + stop("Must supply either a login password or SSH key", call.=FALSE) + if(pwd && key) + stop("Supply either a login password or SSH key, but not both", call.=FALSE) + + if(key && file.exists(sshkey)) + sshkey <- readLines(sshkey) + + structure(list(user=username, key=sshkey, pwd=password), class="user_config") +} + + +#' @rdname vm_resource_config +#' @export +datadisk_config <- function(size, name="datadisk", create="empty", type="StandardSSD_LRS", write_accelerator=FALSE) +{ + vm_caching <- if(type == "Premium_LRS") "ReadOnly" else "None" + vm_create <- if(create == "empty") "attach" else "fromImage" + vm_storage <- if(create == "empty") NULL else type + + vm_spec <- list( + createOption=vm_create, + caching=vm_caching, + writeAcceleratorEnabled=write_accelerator, + storageAccountType=vm_storage, + diskSizeGB=NULL, + id=NULL, + name=name + ) + + res_spec <- if(!is.null(size)) + list( + diskSizeGB=size, + sku=type, + creationData=list(createOption=create), + name=name + ) + else NULL + + structure(list(res_spec=res_spec, vm_spec=vm_spec), class="datadisk_config") +} + + +#' @rdname vm_resource_config +#' @export +image_config <- function(publisher=NULL, offer=NULL, sku=NULL, version="latest", id=NULL) +{ + if(!is.null(publisher) && !is.null(offer) && !is.null(sku)) + { + structure(list(publisher=publisher, offer=offer, sku=sku, version=version), + class=c("image_marketplace", "image_config")) + } + else if(!is.null(id)) + { + structure(list(id=id), + class=c("image_custom", "image_config")) + } + else stop("Invalid image configuration", call.=FALSE) +} + diff --git a/R/vm_template_builders.R b/R/vm_template_builders.R new file mode 100644 index 0000000..c851eb6 --- /dev/null +++ b/R/vm_template_builders.R @@ -0,0 +1,122 @@ +add_template_parameters.vm_config <- function(config, ...) +{ + add_param <- function(...) + { + new_params <- lapply(list(...), function(obj) list(type=obj)) + params <<- c(params, new_params) + } + + params <- tpl_parameters_default + + if(config$keylogin) + add_param(sshKeyData="string") + else add_param(adminPassword="securestring") + + if(inherits(config$image, "image_marketplace")) + add_param(imagePublisher="string", imageOffer="string", imageSku="string", imageVersion="string") + else add_param(imageId="string") + + if(length(config$datadisks) > 0) + add_param(dataDisks="array", dataDiskResources="array") + + params +} + + +add_template_variables.vm_config <- function(config, ...) +{ + vars <- list( + location="[resourceGroup().location]", + vmId="[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]", + vmRef="[concat('Microsoft.Compute/virtualMachines/', parameters('vmName'))]" + ) + + for(res in c("nsg", "ip", "vnet", "nic")) + vars <- c(vars, add_template_variables(config[[res]], res)) + + # add any extra variables provided by the user + utils::modifyList(vars, config$variables) +} + + +add_template_resources.vm_config <- function(config, ...) +{ + vm <- vm_default + + # fixup VM properties + n_disks <- length(config$datadisks) + n_disk_resources <- if(n_disks > 0) + sum(sapply(config$datadisks, function(x) !is.null(x$res_spec))) + else 0 + + if(n_disks > 0) + { + vm$properties$storageProfile$copy <- vm_datadisk + if(n_disk_resources > 0) + vm$dependsOn <- c(vm$dependsOn, "managedDiskResources") + } + + if(config$managed) + vm$identity <- list(type="systemAssigned") + + vm$properties$osProfile <- c(vm$properties$osProfile, + if(config$keylogin) vm_key_login else vm_pwd_login) + + if(inherits(config$image, "image_custom")) + vm$properties$storageProfile$imageReference <- list(id="[parameters('imageId')]") + + if(!is_empty(config$vm_fields)) + vm <- utils::modifyList(vm, config$vm_fields) + + resources <- config[c("nsg", "ip", "vnet", "nic")] + + existing <- sapply(resources, existing_resource) + unused <- sapply(resources, is.null) + create <- !existing & !unused + + # cannot use lapply(*, build_resource_fields) because of lapply wart + resources <- lapply(resources[create], function(x) build_resource_fields(x)) + + ## fixup dependencies between resources + # vnet depends on nsg + # nic depends on ip, vnet (possibly nsg) + # vm depends on nic (but nic should always be created) + + if(create["vnet"]) + { + if(!create["nsg"]) + resources$vnet$dependsOn <- NULL + + if(unused["nsg"]) + resources$vnet$properties$subnets[[1]]$properties$networkSecurityGroup <- NULL + } + + if(create["nic"]) + { + nic_created_depends <- create[c("ip", "vnet")] + resources$nic$dependsOn <- resources$nic$dependsOn[nic_created_depends] + if(unused["ip"]) + resources$nic$properties$ipConfigurations[[1]]$properties$publicIPAddress <- NULL + } + else vm$dependsOn <- NULL + + if(n_disk_resources > 0) + resources <- c(resources, list(disk_default)) + + resources <- c(resources, list(vm)) + + if(!is_empty(config$other)) + resources <- c(resources, lapply(config$other, function(x) build_resource_fields(x))) + + unname(resources) +} + + +# check if we are referring to an existing resource or creating a new one +existing_resource <- function(object) +{ + # can be a resource ID string or AzureRMR::az_resource object + is.character(object) || is_resource(object) +} + + diff --git a/R/vmss_config.R b/R/vmss_config.R new file mode 100644 index 0000000..665e87b --- /dev/null +++ b/R/vmss_config.R @@ -0,0 +1,316 @@ +#' Virtual machine scaleset configuration functions +#' +#' @param image For `vmss_config`, the VM image to deploy. This should be an object of class `image_config`, created by the function of the same name. +#' @param options Scaleset options, as obtained via a call to `scaleset_options`. +#' @param nsg The network security group for the scaleset. Can be a call to `nsg_config` to create a new NSG; an AzureRMR resource object or resource ID to reuse an existing NSG; or NULL to not use an NSG (not recommended). +#' @param vnet The virtual network for the scaleset. Can be a call to `vnet_config` to create a new virtual network, or an AzureRMR resource object or resource ID to reuse an existing virtual network. Note that by default, AzureVM will associate the NSG with the virtual network/subnet, not with the VM's network interface. +#' @param load_balancer The load balancer for the scaleset. Can be a call to `lb_config` to create a new load balancer; an AzureRMR resource object or resource ID to reuse an existing load balancer; or NULL if load balancing is not required. +#' @param load_balancer_address The public IP address for the load balancer. Can be a call to `ip_config` to create a new IP address, or an AzureRMR resource object or resource ID to reuse an existing address resource. Ignored if `load_balancer` is NULL. +#' @param autoscaler The autoscaler for the scaleset. Can be a call to `autoscaler_config` to create a new autoscaler; an AzureRMR resource object or resource ID to reuse an existing autoscaler; or NULL if autoscaling is not required. +#' @param other_resources An optional list of other resources to include in the deployment. +#' @param variables An optional named list of variables to add to the template. +#' @param ... For the specific VM configurations, other customisation arguments to be passed to `vm_config`. For `vmss_config`, named arguments that will be folded into the scaleset resource definition in the template. +#' +#' @details +#' These functions are for specifying the details of a new virtual machine scaleset deployment: the base VM image and related options, along with the Azure resources that the scaleset may need. These include the network security group, virtual network, load balancer and associated public IP address, and autoscaler. +#' +#' Each resource can be specified in a number of ways: +#' - To _create_ a new resource as part of the deployment, call the corresponding `*_config` function. +#' - To use an _existing_ resource, supply either an `AzureRMR::az_resource` object (recommended) or a string containing the resource ID. +#' - If the resource is not needed, specify it as NULL. +#' - For the `other_resources` argument, supply a list of resources, each of which should be a list of resource fields (name, type, properties, sku, etc). +#' +#' The `vmss_config` function is the base configuration function, and the others call it to create VM scalesets with specific operating systems and other image details. +#' - `ubuntu_dsvm_ss`: Data Science Virtual Machine, based on Ubuntu 16.04 +#' - `windows_dsvm_ss`: Data Science Virtual Machine, based on Windows Server 2016 +#' - `ubuntu_16.04_ss`, `ubuntu_18.04`: Ubuntu +#' - `windows_2016_ss`, `windows_2019`: Windows Server Datacenter edition +#' - `rhel_7.6_ss`, `rhel_8_ss`: Red Hat Enterprise Linux +#' - `debian_9_backports_ss`: Debian +#' +#' @return +#' An object of S3 class `vmss_config`, that can be used by the `create_vm_scaleset` method. +#' +#' @seealso +#' [scaleset_options] for options relating to the scaleset resource itself +#' +#' [nsg_config], [ip_config], [vnet_config], [lb_config], [autoscaler_config] for other resource configs +#' +#' [vm_config] for configuring an individual virtual machine +#' +#' [create_vm_scaleset] +#' +#' @examples +#' +#' # basic Linux (Ubuntu) and Windows configs +#' ubuntu_18.04_ss() +#' windows_2019_ss() +#' +#' # Windows DSVM scaleset, no load balancer and autoscaler +#' windows_dsvm_ss(load_balancer=NULL, autoscaler=NULL) +#' +#' # RHEL VM exposing ports 80 (HTTP) and 443 (HTTPS) +#' rhel_8_ss(nsg=nsg_config(nsg_rule_allow_http, nsg_rule_allow_https)) +#' +#' # exposing no ports externally +#' rhel_8_ss(nsg=nsg_config(list())) +#' +#' # low-priority VMs, large scaleset (>100 instances allowed), no managed identity +#' rhel_8_ss(options=scaleset_options(low_priority=TRUE, large_scaleset=TRUE, managed=FALSE)) +#' +#' +#' \dontrun{ +#' +#' # reusing existing resources: placing a scaleset in an existing vnet/subnet +#' # we don't need a new network security group either +#' vnet <- AzureRMR::get_azure_login()$ +#' get_subscription("sub_id")$ +#' get_resource_group("rgname")$ +#' get_resource(type="Microsoft.Network/virtualNetworks", name="myvnet") +#' +#' ubuntu_18.04_ss(vnet=vnet, nsg=NULL) +#' +#' } +#' @export +vmss_config <- function(image, options=scaleset_options(), + nsg=nsg_config(), + vnet=vnet_config(), + load_balancer=lb_config(), + load_balancer_address=ip_config(), + autoscaler=autoscaler_config(), + other_resources=list(), + variables=list(), + ...) +{ + stopifnot(inherits(image, "image_config")) + stopifnot(inherits(options, "scaleset_options")) + + # make IP sku, balancer sku and scaleset size consistent with each other + load_balancer <- vmss_fixup_lb(options, load_balancer) + ip <- vmss_fixup_ip(options, load_balancer, load_balancer_address) + + obj <- list( + image=image, + options=options, + nsg=nsg, + vnet=vnet, + lb=load_balancer, + ip=ip, + as=autoscaler, + other=other_resources, + variables=variables, + vmss_fields=list(...) + ) + structure(obj, class="vmss_config") +} + + +vmss_fixup_lb <- function(options, lb) +{ + # don't try to fix load balancer if not created here + if(is.null(lb) || !inherits(lb, "lb_config")) + return(lb) + + # for a large scaleset, must set sku=standard + if(!options$params$singlePlacementGroup) + { + if(is.null(lb$type)) + lb$type <- "standard" + else if(tolower(lb$type) != "standard") + stop("Load balancer type must be 'standard' for large scalesets", call.=FALSE) + } + else + { + if(is.null(lb$type)) + lb$type <- "basic" + } + + lb +} + + +vmss_fixup_ip <- function(options, lb, ip) +{ + # IP address only required if load balancer is present + if(is.null(lb)) + return(NULL) + + # don't try to fix IP if load balancer was provided as a resource id + if(is.character(lb)) + return(ip) + + # don't try to fix IP if not created here + if(is.null(ip) || !inherits(ip, "ip_config")) + return(ip) + + lb_type <- if(is_resource(lb)) + lb$sku$name + else lb$type + + # for a large scaleset, must set sku=standard, allocation=static + if(!options$params$singlePlacementGroup) + { + if(is.null(ip$type)) + ip$type <- "standard" + else if(tolower(ip$type) != "standard") + stop("Load balancer IP address type must be 'standard' for large scalesets", call.=FALSE) + + if(is.null(ip$dynamic)) + ip$dynamic <- FALSE + else if(ip$dynamic) + stop("Load balancer dynamic IP address not supported for large scalesets", call.=FALSE) + } + else + { + # defaults for small scaleset: sku=load balancer sku, allocation=dynamic + if(is.null(ip$type)) + ip$type <- lb_type + if(is.null(ip$dynamic)) + ip$dynamic <- tolower(ip$type) == "basic" + } + + # check consistency + if(tolower(ip$type) == "standard" && ip$dynamic) + stop("Standard IP address type does not support dynamic allocation", call.=FALSE) + + ip +} + + +#' @rdname vmss_config +#' @export +ubuntu_dsvm_ss <- function(nsg=nsg_config(list(nsg_rule_allow_ssh, nsg_rule_allow_jupyter, nsg_rule_allow_rstudio)), + load_balancer=lb_config(rules=list(lb_rule_ssh, lb_rule_jupyter, lb_rule_rstudio), + probes=list(lb_probe_ssh, lb_probe_jupyter, lb_probe_rstudio)), + ...) +{ + vmss_config(image_config("microsoft-dsvm", "linux-data-science-vm-ubuntu", "linuxdsvmubuntu"), + nsg=nsg, load_balancer=load_balancer, ...) +} + +#' @rdname vmss_config +#' @export +windows_dsvm_ss <- function(nsg=nsg_config(list(nsg_rule_allow_rdp)), + load_balancer=lb_config(rules=list(lb_rule_rdp), + probes=list(lb_probe_rdp)), + options=scaleset_options(keylogin=FALSE), + ...) +{ + options$keylogin <- FALSE + vmss_config(image_config("microsoft-dsvm", "dsvm-windows", "server-2016"), + options=options, nsg=nsg, load_balancer=load_balancer, ...) +} + +#' @rdname vmss_config +#' @export +ubuntu_16.04_ss <- function(nsg=nsg_config(list(nsg_rule_allow_ssh)), + load_balancer=lb_config(rules=list(lb_rule_ssh), + probes=list(lb_probe_ssh)), + ...) +{ + vmss_config(image_config("Canonical", "UbuntuServer", "16.04-LTS"), + nsg=nsg, load_balancer=load_balancer, ...) +} + +#' @rdname vmss_config +#' @export +ubuntu_18.04_ss <- function(nsg=nsg_config(list(nsg_rule_allow_ssh)), + load_balancer=lb_config(rules=list(lb_rule_ssh), + probes=list(lb_probe_ssh)), + ...) +{ + vmss_config(image_config("Canonical", "UbuntuServer", "18.04-LTS"), + nsg=nsg, load_balancer=load_balancer, ...) +} + +#' @rdname vmss_config +#' @export +windows_2016_ss <- function(nsg=nsg_config(list(nsg_rule_allow_rdp)), + load_balancer=lb_config(rules=list(lb_rule_rdp), + probes=list(lb_probe_rdp)), + options=scaleset_options(keylogin=FALSE), + ...) +{ + options$keylogin <- FALSE + vmss_config(image_config("MicrosoftWindowsServer", "WindowsServer", "2016-Datacenter"), + options=options, nsg=nsg, load_balancer=load_balancer, ...) +} + +#' @rdname vmss_config +#' @export +windows_2019_ss <- function(nsg=nsg_config(list(nsg_rule_allow_rdp)), + load_balancer=lb_config(rules=list(lb_rule_rdp), + probes=list(lb_probe_rdp)), + options=scaleset_options(keylogin=FALSE), + ...) +{ + options$keylogin <- FALSE + vmss_config(image_config("MicrosoftWindowsServer", "WindowsServer", "2019-Datacenter"), + options=options, nsg=nsg, load_balancer=load_balancer, ...) +} + +#' @rdname vmss_config +#' @export +rhel_7.6_ss <- function(nsg=nsg_config(list(nsg_rule_allow_ssh)), + load_balancer=lb_config(rules=list(lb_rule_ssh), + probes=list(lb_probe_ssh)), + ...) +{ + vmss_config(image_config("RedHat", "RHEL", "7-RAW"), + nsg=nsg, load_balancer=load_balancer, ...) +} + +#' @rdname vmss_config +#' @export +rhel_8_ss <- function(nsg=nsg_config(list(nsg_rule_allow_ssh)), + load_balancer=lb_config(rules=list(lb_rule_ssh), + probes=list(lb_probe_ssh)), + ...) +{ + vmss_config(image_config("RedHat", "RHEL", "8"), + nsg=nsg, load_balancer=load_balancer, ...) +} + +#' @rdname vmss_config +#' @export +debian_9_backports_ss <- function(nsg=nsg_config(list(nsg_rule_allow_ssh)), + load_balancer=lb_config(rules=list(lb_rule_ssh), + probes=list(lb_probe_ssh)), + ...) +{ + vmss_config(image_config("Credativ", "Debian", "9-backports"), + nsg=nsg, load_balancer=load_balancer, ...) +} + + +#' Virtual machine scaleset options +#' +#' @param keylogin Boolean: whether to use an SSH public key to login (TRUE) or a password (FALSE). Note that Windows does not support SSH key logins. +#' @param managed Whether to provide a managed system identity for the VM. +#' @param public Whether the instances (nodes) of the scaleset should be visible from the public internet. +#' @param low_priority Whether to use low-priority VMs. Note that this option is only available for certain VM sizes. +#' @param delete_on_evict If low-priority VMs are being used, whether evicting (shutting down) a VM should delete it, as opposed to just deallocating it. +#' @param network_accel Whether to enable accelerated networking. +#' @param large_scaleset Whether to enable scaleset sizes > 100 instances. +#' @param overprovision Whether to overprovision the scaleset on creation. +#' @param upgrade_policy A list, giving the VM upgrade policy for the scaleset. +#' +#' @export +scaleset_options <- function(keylogin=TRUE, managed=TRUE, public=FALSE, + low_priority=FALSE, delete_on_evict=FALSE, + network_accel=FALSE, large_scaleset=FALSE, + overprovision=TRUE, upgrade_policy=list(mode="manual")) +{ + params <- list( + priority=if(low_priority) "low" else "regular", + evictionPolicy=if(delete_on_evict) "delete" else "deallocate", + enableAcceleratedNetworking=network_accel, + singlePlacementGroup=!large_scaleset, + overprovision=overprovision, + upgradePolicy=upgrade_policy + ) + + out <- list(keylogin=keylogin, managed=managed, public=public, params=params) + structure(out, class="scaleset_options") +} + diff --git a/R/vmss_template_builders.R b/R/vmss_template_builders.R new file mode 100644 index 0000000..518c4db --- /dev/null +++ b/R/vmss_template_builders.R @@ -0,0 +1,130 @@ +add_template_parameters.vmss_config <- function(config, ...) +{ + add_param <- function(...) + { + new_params <- lapply(list(...), function(obj) list(type=obj)) + params <<- c(params, new_params) + } + + params <- sstpl_parameters_default + + if(config$options$keylogin) + add_param(sshKeyData="string") + else add_param(adminPassword="securestring") + + if(inherits(config$image, "image_marketplace")) + add_param(imagePublisher="string", imageOffer="string", imageSku="string", imageVersion="string") + else add_param(imageId="string") + + params +} + + +add_template_variables.vmss_config <- function(config, ...) +{ + vars <- list( + location="[resourceGroup().location]", + vmId="[resourceId('Microsoft.Compute/virtualMachineScalesets', parameters('vmName'))]", + vmRef="[concat('Microsoft.Compute/virtualMachineScalesets/', parameters('vmName'))]", + vmPrefix="[concat(parameters('vmName'), '-instance')]" + ) + + for(res in c("nsg", "vnet", "lb", "ip", "as")) + vars <- c(vars, add_template_variables(config[[res]], res)) + + # add any extra variables provided by the user + utils::modifyList(vars, config$variables) +} + + +add_template_resources.vmss_config <- function(config, ...) +{ + vmss <- vmss_default + + # fixup VMSS properties + if(config$options$managed) + vmss$identity <- list(type="systemAssigned") + + # fixup VM properties + vm <- vmss$properties$virtualMachineProfile + + vm$osProfile <- c(vm$osProfile, + if(config$options$keylogin) vm_key_login else vm_pwd_login) + + vm$storageProfile$imageReference <- if(inherits(config$image, "image_custom")) + list(id="[parameters('imageId')]") + else list( + publisher="[parameters('imagePublisher')]", + offer="[parameters('imageOffer')]", + sku="[parameters('imageSku')]", + version="[parameters('imageVersion')]" + ) + + if(config$options$params$priority == "low") + vm$evictionPolicy <- "[parameters('evictionPolicy')]" + + if(!is_empty(config$lb)) + { + vm$ + networkProfile$ + networkInterfaceConfigurations[[1]]$ + properties$ + ipConfigurations[[1]]$ + properties$ + loadBalancerBackendAddressPools <- list(list(id="[variables('lbBackendId')]")) # lol + } + + if(config$options$public) + { + vm$ + networkProfile$ + networkInterfaceConfigurations[[1]]$ + properties$ + ipConfigurations[[1]]$ + properties$ + publicIpAddressConfiguration <- list( + name="pub1", + properties=list(idleTimeoutInMinutes=15) + ) + } + + vmss$properties$virtualMachineProfile <- vm + if(!is_empty(config$vmss_fields)) + vmss <- utils::modifyList(vmss, config$vmss_fields) + + resources <- config[c("nsg", "vnet", "lb", "ip", "as")] + + existing <- sapply(resources, existing_resource) + unused <- sapply(resources, is.null) + create <- !existing & !unused + + # cannot use lapply(*, build_resource_fields) because of lapply wart + resources <- lapply(resources[create], function(x) build_resource_fields(x)) + + ## fixup dependencies between resources + # vnet depends on nsg + # vmss depends on vnet, lb + + if(create["vnet"]) + { + if(!create["nsg"]) + resources$vnet$dependsOn <- NULL + + if(unused["nsg"]) + resources$vnet$properties$subnets[[1]]$properties$networkSecurityGroup <- NULL + } + + vmss_depends <- character() + if(create["lb"]) + vmss_depends <- c(vmss_depends, "[variables('lbRef')]") + if(create["vnet"]) + vmss_depends <- c(vmss_depends, "[variables('vnetRef')]") + vmss$dependsOn <- I(vmss_depends) + + resources <- c(resources, list(vmss)) + + if(!is_empty(config$other)) + resources <- c(resources, lapply(config$other, function(x) build_resource_fields(x))) + + unname(resources) +} diff --git a/R/vnet_config.R b/R/vnet_config.R new file mode 100644 index 0000000..bef68fe --- /dev/null +++ b/R/vnet_config.R @@ -0,0 +1,86 @@ +#' Virtual network configuration +#' +#' @param address_space For `vnet_config`, the address range accessible by the virtual network, expressed in CIDR block format. +#' @param subnets For `vnet_config`, a list of subnet objects, each obtained via a call to `subnet_config`. +#' @param name For `subnet_config`, the name of the subnet. Duplicate names will automatically be disambiguated prior to VM deployment. +#' @param addresses For `subnet_config`, the address ranges spanned by this subnet. Must be a subset of the address space available to the parent virtual network. +#' @param nsg The network security group associated with this subnet. Defaults to the NSG created as part of this VM deployment. +#' @param ... Other named arguments that will be treated as resource properties. +#' +#' @seealso +#' [create_vm], [vm_config], [vmss_config] +#' @export +vnet_config <- function(address_space="10.0.0.0/16", subnets=list(subnet_config()), ...) +{ + # attempt to fixup address blocks so they are consistent (should use iptools when it's fixed) + ab_regex <- "^([0-9]+\\.[0-9]+).*$" + ab_block <- sub(ab_regex, "\\1", address_space) + fixaddr <- function(addr) + { + if(sub(ab_regex, "\\1", addr) == ab_block) + sub("^[0-9]+\\.[0-9]+", ab_block, addr) + else addr + } + subnets <- lapply(subnets, function(sn) + { + if(!is_empty(sn$properties$addressPrefix)) + sn$properties$addressPrefix <- fixaddr(sn$properties$addressPrefix) + if(!is_empty(sn$properties$addressPrefixes)) + sn$properties$addressPrefixes <- sapply(sn$properties$addressPrefixes, fixaddr) + + sn + }) + + # unique-ify subnet names + sn_names <- make.unique(sapply(subnets, `[[`, "name")) + for(i in seq_along(subnets)) + subnets[[i]]$name <- sn_names[i] + + props <- list( + addressSpace=list(addressPrefixes=I(address_space)), + subnets=subnets, + ... + ) + structure(list(properties=props), class="vnet_config") +} + + +build_resource_fields.vnet_config <- function(config, ...) +{ + config$properties$subnets <- lapply(config$properties$subnets, unclass) + utils::modifyList(vnet_default, config) +} + + +add_template_variables.vnet_config <- function(config, ...) +{ + name <- "[concat(parameters('vmName'), '-vnet')]" + id <- "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" + ref <- "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]" + + subnet <- config$properties$subnets[[1]]$name + subnet_id <- "[concat(variables('vnetId'), '/subnets/', variables('subnet'))]" + + list(vnetName=name, vnetId=id, vnetRef=ref, subnet=subnet, subnetId=subnet_id) +} + + +#' @rdname vnet_config +#' @export +subnet_config <- function(name="subnet", addresses="10.0.0.0/16", nsg="[variables('nsgId')]", ...) +{ + properties <- if(length(addresses) < 2) + list(addressPrefix=addresses, ...) + else list(addressPrefixes=addresses, ...) + + # check if supplied a network security group resource ID or object + if(is.character(nsg)) + properties$networkSecurityGroup$id <- nsg + else if(is_resource(nsg) && tolower(nsg$type) == "microsoft.network/networksecuritygroups") + properties$networkSecurityGroup$id <- nsg$id + else if(!is.null(nsg)) + warning("Invalid network security group", call.=FALSE) + + subnet <- list(name=name, properties=properties) + structure(subnet, class="subnet_config") +} diff --git a/README.md b/README.md index 4161f38..7e717a2 100644 --- a/README.md +++ b/README.md @@ -4,77 +4,165 @@ ![Downloads](https://cranlogs.r-pkg.org/badges/AzureVM) [![Travis Build Status](https://travis-ci.org/Azure/AzureVM.png?branch=master)](https://travis-ci.org/Azure/AzureVM) -This is a package for interacting with virtual machines in Azure. You can deploy, start up, shut down, run scripts, deallocate and delete VMs from the R command line. A number of VM templates are included based on the [Data Science Virtual Machine](https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/), but you can also deploy other templates that you supply. +AzureVM is a package for interacting with virtual machines and virtual machine scalesets in Azure. You can deploy, start up, shut down, run scripts, deallocate and delete VMs and scalesets from the R command line. It uses the tools provided by the [AzureRMR package](https://github.com/Azure/AzureRMR) to manage VM resources and templates. -AzureVM uses the tools provided by the [AzureRMR package](https://github.com/Azure/AzureRMR) to manage VM resources and templates. The main VM R6 class wraps the deployment template, allowing easy management of all resources as a unit. You can also create a VM in exclusive mode, meaning that it sits in its own resource group. +## Virtual machines -The package supports both single VMs as well as clusters. A single VM is treated as a cluster containing only one node. - -A sample workflow: +Here is a simple example. We create a VM using the default settings, run a shell command, and then delete the VM. ```r -library(AzureRMR) library(AzureVM) -# authenticate with Resource Manager -az <- az_rm$new(tenant="xxx-xxx-xxx", app="yyy-yyy-yyy", secret="{secret goes here}") - -sub1 <- az$get_subscription("5710aa44-281f-49fe-bfa6-69e66bb55b11") - -# list VM sizes -- a large data frame -sub1$list_vm_sizes(location="australiasoutheast") -# name numberOfCores osDiskSizeInMB resourceDiskSizeInMB memoryInMB maxDataDiskCount -# 1 Standard_A0 1 1047552 20480 768 1 -# 2 Standard_A1 1 1047552 71680 1792 2 -# 3 Standard_A2 2 1047552 138240 3584 4 -# 4 Standard_A3 4 1047552 291840 7168 8 -# 5 Standard_A5 2 1047552 138240 14336 4 -# 6 Standard_A4 8 1047552 619520 14336 16 -# ... - -# create a new Ubuntu VM in an existing resource group -rg <- sub1$get_resource_group("rdev1") -key <- readLines("~/id_rsa.pub") -rdevtest <- rg$create_vm("rdevtest", username="user", passkey=key, userauth_type="key", os="Ubuntu", - location="australiasoutheast") -rdevtest -# -# Operating system: Linux -# Exclusive resource group: FALSE -# Domain name: rdevtest.australiasoutheast.cloudapp.azure.com -# Status: -# ProvisioningState PowerState -# rdevtest succeeded running -#--- -# disks: list(rdevtest) -# id: /subscriptions/5710aa44-281f-49fe-bfa6-69e66bb55b11/resourceGroups/rdev1/providers/Microsoft.Resou ... -# ip_address: xxx.xxx.xxx.xxx -# properties: list(templateHash, parameters, mode, debugSetting, provisioningState, timestamp, duration, -# correlationId, providers, dependencies, outputs, outputResources) -#--- -# Methods: -# add_extension, cancel, check, delete, restart, run_deployed_command, run_script, start, stop, -# sync_vm_status - -# shut down the VM -rdevtest$stop() - -# ... and delete it (this will take some time) -rdevtest$delete() - +sub <- AzureRMR::get_azure_login()$ + get_subscription("5710aa44-281f-49fe-bfa6-69e66bb55b11") # calling create_vm() from a subscription object will create the VM in its own resource group -rdevtest2 <- sub1$create_vm("rdevtest2", username="user", passkey=key, userauth_type="key", os="Ubuntu", - location="australiasoutheast") +# default is an Ubuntu 18.04 VM, size Standard_DS3_v2, login via SSH key +# call sub$list_vm_sizes() to get the sizes available in your region +vm <- sub$create_vm("myubuntuvm", user_config("myname", "~/.ssh/id_rsa.pub"), + location="australiaeast") # run a shell script or command remotely (will be PowerShell on a Windows VM) -rdevtest2$run_script("ifconfig > /tmp/ifc.txt") +vm$run_script("ifconfig > /tmp/ifc.txt") # ... and stop it -rdevtest2$stop() +vm$stop() -# .. and delete it (this can be done asynchronously for a VM in its own group) -rdevtest2$delete() +# ... and resize it +vm$resize("Standard_DS4_v2") + +# ... and delete it (this can be done asynchronously for a VM in its own group) +vm$delete() +``` + +AzureVM comes with a number of predefined configurations, for deploying commonly used VM images. For example, to create an Ubuntu DSVM accessible via SSH, JupyterHub and RStudio Server: + +```r +sub$create_vm("mydsvm", user_config("myname", "~/.ssh/id_rsa.pub"), config="ubuntu_dsvm", + location="australiaeast") +``` +And to create a Windows Server 2019 VM, accessible via RDP: + +```r +sub$create_vm("mywinvm", user_config("myname", password="Use-strong-passwords!"), config="windows_2019", + location="australiaeast") +``` + +The available predefined configurations are `ubuntu_18.04` (the default), `ubuntu_16.04`, `ubuntu_dsvm`, `windows_2019`, `windows_2016`, `windows_dsvm`, `rhel_7.6`, `rhel_8` and `debian_9_backports`. You can combine these with several other arguments to customise the VM deployment to your needs: + +- `size`: VM size. Use the `list_vm_sizes` method for the subscription and resource group classes to see the available sizes. +- `datadisks`: The data disk sizes/configurations to attach. +- `ip`: Public ip address. Set this to NULL if you don't want the VM to be accessible outside its subnet. +- `vnet`: Virtual network/subnet. +- `nsg`: Network security group. AzureVM will associate the NSG with the vnet/subnet, not with the VM's network interface. +- `nic`: Network interface. +- `other_resources`: Optionally, a list of other resources to deploy. + +```r +# Windows Server 2016, with a 500GB datadisk attached, not publicly accessible +sub$create_vm("mywinvm2", user_config("myname", password="Use-strong-passwords!"), + size="Standard_DS4_v2", config="windows_2016", datadisks=500, ip=NULL, + location="australiaeast") + +# Ubuntu DSVM, GPU-enabled +sub$create_vm("mydsvm", user_config("myname", "~/.ssh/id_rsa.pub"), size="Standard_NC12", + config="ubuntu_dsvm_ss", + location="australiaeast") + +# Red Hat VM, serving HTTP/HTTPS +sub$create_vm("myrhvm", user_config("myname", "~/.ssh/id_rsa.pub"), config="rhel_8", + nsg=nsg_config(list(nsg_rule_allow_http, nsg_rule_allow_https)), + location="australiaeast") +``` + +Full customisation is provided by the `vm_config` function, which also lets you specify the image to deploy, either from the marketplace or a disk. (The predefined configurations actually call `vm_config`, with the appropriate arguments for each specific config.) + +```r +## custom VM configuration: Windows 10 Pro 1903 with data disks +## this assumes you have a valid Win10 desktop license +user <- user_config("myname", password="Use-strong-passwords!") +image <- image_config( + publisher="MicrosoftWindowsDesktop", + offer="Windows-10", + sku="19h1-pro" +) +datadisks <- list( + datadisk_config(250, type="Premium_LRS"), + datadisk_config(1000, type="Standard_LRS") +) +nsg <- nsg_config( + list(nsg_rule_allow_rdp) +) +sub$create_vm("mywin10vm", user, + config=vm_config( + image=image, + keylogin=FALSE, + datadisks=datadisks, + nsg=nsg, + properties=list(licenseType="Windows_Client") + ), + location="australiaeast" +) +``` + +## VM scalesets + +The equivalent to `create_vm` for scalesets is the `create_vm_scaleset` method. By default, a new scaleset will come with a load balancer and autoscaler attached, but its instances will not be externally accessible. + +```r +# default is Ubuntu 18.04 scaleset, size Standard_DS1_v2 +sub$create_vm_scaleset("myubuntuss", user_config("myname", "~/.ssh/id_rsa.pub"), instances=5, + location="australiaeast") +``` + +Each predefined VM configuration has a corresponding scaleset configuration. To specify low-level scaleset settings, use the `scaleset_options` argument. Here are some sample scaleset deployments: + +```r +# Windows Server 2019 +sub$create_vm_scaleset("mywinss", user_config("myname", password="Use-strong-passwords!"), instances=5, + config="windows_2019", + location="australiaeast") + +# RHEL scaleset, serving HTTP/HTTPS +sub$create_vm_scaleset("myrhelss", user_config("myname", "~/.ssh/id_rsa.pub"), instances=5, + config="rhel_8_ss", + nsg=nsg_config(list(nsg_rule_allow_http, nsg_rule_allow_https)), + location="australiaeast") + +# Ubuntu DSVM, GPU-enabled, public instances, no load balancer or autoscaler +sub$create_vm_scaleset("mydsvmss", user_config("myname", "~/.ssh/id_rsa.pub"), instances=5, + size="Standard_NC6", config="ubuntu_dsvm_ss", + options=scaleset_options(public=TRUE), + load_balancer=NULL, autoscaler=NULL, + location="australiaeast") + +# Large Debian scaleset (multiple placement groups), using low-priority VMs +# need to set the instance size to something that supports low-pri +sub$create_vm_scaleset("mydebss", user_config("myname", "~/.ssh/id_rsa.pub"), instances=10, + size="Standard_DS3_v2", config="debian_9_backports_ss", + options=scaleset_options(low_priority=TRUE, large_scaleset=TRUE), + location="australiaeast") +``` + +## Sharing resources + +You can also include an existing Azure resource in a deployment, by supplying an AzureRMR `az_resource` object as an argument in the `create_vm` or `create_vm_scaleset` call. For example, here we create a VM and a scaleset that share a single virtual network/subnet. + +```r +## VM and scaleset in the same resource group and virtual network +# first, create the resgroup +rg <- sub$create_resource_group("rgname", "australiaeast") + +# create the master +rg$create_vm("mastervm", user_config("myname", "~/.ssh/id_rsa.pub")) + +# get the vnet resource +vnet <- rg$get_resource(type="Microsoft.Network/virtualNetworks", name="mastervm-vnet") + +# create the scaleset +# since the NSG is associated with the vnet, we don't need to create a new NSG either +rg$create_vm_scaleset("slavess", user_config("myname", "~/.ssh/id_rsa.pub"), + instances=5, vnet=vnet, nsg=NULL, load_balancer=NULL, autoscaler=NULL) ``` --- diff --git a/inst/templates/ubuntu_dsvm.json b/inst/templates/ubuntu_dsvm.json deleted file mode 100644 index 37f91e0..0000000 --- a/inst/templates/ubuntu_dsvm.json +++ /dev/null @@ -1,210 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "adminUsername": { - "type": "string", - "metadata": { - "description": "Username for the Virtual Machine." - } - }, - "adminPassword": { - "type": "securestring", - "metadata": { - "description": "Password for the Virtual Machine." - } - }, - "vmName": { - "type": "string", - "metadata": { - "description": "Name Prefix for the Virtual Machine." - } - }, - "vmSize": { - "type": "string", - "defaultValue": "Standard_DS3_v2", - "metadata": { - "description": "Size for the Virtual Machine." - } - } - }, - "variables": { - "location": "[resourceGroup().location]", - "imagePublisher": "microsoft-dsvm", - "imageOffer": "linux-data-science-vm-ubuntu", - "OSDiskName": "[concat(parameters('vmName'),'-osdisk')]", - "DataDiskName": "[concat(parameters('vmName'),'-data-0')]", - "sku": "linuxdsvmubuntu", - "nicName": "[parameters('vmName')]", - "addressPrefix": "10.0.0.0/16", - "subnetName": "Subnet", - "subnetPrefix": "10.0.0.0/24", - "publicIPAddressType": "Dynamic", - "publicIPAddressName": "[parameters('vmName')]", - "vmName": "[parameters('vmName')]", - "vmSize": "[parameters('vmSize')]", - "virtualNetworkName": "[parameters('vmName')]", - "nsgName": "[concat(parameters('vmName'),'-nsg')]", - "nsgId": "[resourceId(resourceGroup().name, 'Microsoft.Network/networkSecurityGroups', variables('nsgName'))]", - "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]", - "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]" - }, - "resources": [ - { - "apiVersion": "2018-02-01", - "type": "Microsoft.Network/networkSecurityGroups", - "location": "[variables('location')]", - "name": "[variables('nsgName')]", - "properties": { - "securityRules": [ - { - "name": "Allow-SSH-Jupyterhub", - "properties": { - "protocol": "Tcp", - "sourcePortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*", - "access": "Allow", - "priority": 100, - "direction": "Inbound", - "sourcePortRanges": [], - "destinationPortRanges": [ - "22", - "8000" - ], - "destinationPortRange": "" - } - } - ] - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/publicIPAddresses", - "name": "[variables('publicIPAddressName')]", - "location": "[variables('location')]", - "properties": { - "publicIPAllocationMethod": "[variables('publicIPAddressType')]", - "dnsSettings": { - "domainNameLabel": "[variables('publicIPAddressName')]" - } - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/virtualNetworks", - "name": "[variables('virtualNetworkName')]", - "location": "[variables('location')]", - "dependsOn": [ - "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]" - ], - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[variables('addressPrefix')]" - ] - }, - "subnets": [ - { - "name": "[variables('subnetName')]", - "properties": { - "addressPrefix": "[variables('subnetPrefix')]" - } - } - ] - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/networkInterfaces", - "name": "[variables('nicName')]", - "location": "[variables('location')]", - "dependsOn": [ - "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]", - "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]" - ], - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "privateIPAllocationMethod": "Dynamic", - "publicIPAddress": { - "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]" - }, - "subnet": { - "id": "[variables('subnetRef')]" - } - } - } - ], - "networkSecurityGroup": { - "id": "[variables('nsgId')]" - } - } - }, - { - "apiVersion": "2017-03-30", - "type": "Microsoft.Compute/virtualMachines", - "name": "[variables('vmName')]", - "location": "[variables('location')]", - "tags": { - "Application": "DataScience" - }, - "dependsOn": [ - "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]" - ], - "properties": { - "hardwareProfile": { - "vmSize": "[variables('vmSize')]" - }, - "osProfile": { - "computerName": "[variables('vmName')]", - "adminUsername": "[parameters('adminUsername')]", - "adminPassword": "[parameters('adminPassword')]" - }, - "storageProfile": { - "imageReference": { - "publisher": "[variables('imagePublisher')]", - "offer": "[variables('imageOffer')]", - "sku": "[variables('sku')]", - "version": "latest" - }, - "osDisk": { - "name": "[variables('OSDiskName')]", - "managedDisk": { - "storageAccountType": "Standard_LRS" - }, - "createOption": "FromImage" - }, - "dataDisks": [ - { - "name": "[variables('DataDiskName')]", - "managedDisk": { - "storageAccountType": "Standard_LRS" - }, - "createOption": "FromImage", - "lun": 0 - } - ] - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]" - } - ] - }, - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": false - } - } - } - } - ], - "outputs": { - "DataScienceVmUrl": { "type": "string", "value": "[concat('https://ms.portal.azure.com/#resource/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Compute/virtualMachines/', variables('vmName'))]" } - } -} diff --git a/inst/templates/ubuntu_dsvm_cl.json b/inst/templates/ubuntu_dsvm_cl.json deleted file mode 100644 index ce5bb5f..0000000 --- a/inst/templates/ubuntu_dsvm_cl.json +++ /dev/null @@ -1,227 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "adminUsername": { - "type": "string", - "metadata": { - "description": "Username for the Virtual Machine." - } - }, - "adminPassword": { - "type": "securestring", - "metadata": { - "description": "Password for the Virtual Machine." - } - }, - "numberOfInstances": { - "type": "string", - "defaultValue": "1", - "metadata": { - "description": "Number of VMs to deploy." - } - }, - "vmName": { - "type": "string", - "metadata": { - "description": "Name Prefix for the Virtual Machine." - } - }, - "vmSize": { - "type": "string", - "defaultValue": "Standard_DS3_v2", - "metadata": { - "description": "Size for the Virtual Machine." - } - } - }, - "variables": { - "location": "[resourceGroup().location]", - "numberOfInstances": "[int(parameters('numberOfInstances'))]", - "imagePublisher": "microsoft-dsvm", - "imageOffer": "linux-data-science-vm-ubuntu", - "sku": "linuxdsvmubuntu", - "nicName": "[parameters('vmName')]", - "addressPrefix": "10.0.0.0/16", - "subnetName": "Subnet", - "subnetPrefix": "10.0.0.0/24", - "publicIPAddressType": "Dynamic", - "publicIPAddressName": "[parameters('vmName')]", - "vmName": "[parameters('vmName')]", - "vmSize": "[parameters('vmSize')]", - "virtualNetworkName": "[parameters('vmName')]", - "nsgName": "[concat(parameters('vmName'),'-nsg')]", - "nsgId": "[resourceId(resourceGroup().name, 'Microsoft.Network/networkSecurityGroups', variables('nsgName'))]", - "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]", - "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]" - }, - "resources": [ - { - "apiVersion": "2018-02-01", - "type": "Microsoft.Network/networkSecurityGroups", - "location": "[variables('location')]", - "name": "[variables('nsgName')]", - "properties": { - "securityRules": [ - { - "name": "Allow-SSH-Jupyterhub", - "properties": { - "protocol": "Tcp", - "sourcePortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*", - "access": "Allow", - "priority": 100, - "direction": "Inbound", - "sourcePortRanges": [], - "destinationPortRanges": [ - "22", - "8000" - ], - "destinationPortRange": "" - } - } - ] - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/publicIPAddresses", - "name": "[concat(variables('publicIPAddressName'), copyindex())]", - "location": "[variables('location')]", - "copy": { - "name": "publicIPLoop", - "count": "[variables('numberOfInstances')]" - }, - "properties": { - "publicIPAllocationMethod": "[variables('publicIPAddressType')]", - "dnsSettings": { - "domainNameLabel": "[concat(variables('publicIPAddressName'), copyindex())]" - } - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/virtualNetworks", - "name": "[variables('virtualNetworkName')]", - "location": "[variables('location')]", - "dependsOn": [ - "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]" - ], - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[variables('addressPrefix')]" - ] - }, - "subnets": [ - { - "name": "[variables('subnetName')]", - "properties": { - "addressPrefix": "[variables('subnetPrefix')]", - "networkSecurityGroup": { - "id": "[variables('nsgId')]" - } - } - } - ] - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/networkInterfaces", - "name": "[concat(variables('nicName'), copyindex())]", - "location": "[variables('location')]", - "copy": { - "name": "nicLoop", - "count": "[variables('numberOfInstances')]" - }, - "dependsOn": [ - "[concat('Microsoft.Network/publicIPAddresses/', concat(variables('publicIPAddressName'),copyindex()))]", - "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" - ], - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "privateIPAllocationMethod": "Dynamic", - "publicIPAddress": { - "id": "[resourceId('Microsoft.Network/publicIPAddresses',concat(variables('publicIPAddressName'),copyindex()))]" - }, - "subnet": { - "id": "[variables('subnetRef')]" - } - } - } - ] - } - }, - { - "apiVersion": "2017-03-30", - "type": "Microsoft.Compute/virtualMachines", - "name": "[concat(variables('vmName'), copyIndex())]", - "location": "[variables('location')]", - "copy": { - "name": "virtualMachineLoop", - "count": "[variables('numberOfInstances')]" - }, - "tags": { - "Application": "DataScience" - }, - "dependsOn": [ - "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'), copyindex())]" - ], - "properties": { - "hardwareProfile": { - "vmSize": "[variables('vmSize')]" - }, - "osProfile": { - "computerName": "[concat(variables('vmName'), copyIndex())]", - "adminUsername": "[parameters('adminUsername')]", - "adminPassword": "[parameters('adminPassword')]" - }, - "storageProfile": { - "imageReference": { - "publisher": "[variables('imagePublisher')]", - "offer": "[variables('imageOffer')]", - "sku": "[variables('sku')]", - "version": "latest" - }, - "osDisk": { - "managedDisk": { - "storageAccountType": "Standard_LRS" - }, - "createOption": "FromImage" - }, - "dataDisks": [ - { - "managedDisk": { - "storageAccountType": "Standard_LRS" - }, - "createOption": "FromImage", - "lun": 0 - } - ] - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces',concat(variables('nicName'), copyindex()))]" - } - ] - }, - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": false - } - } - } - } - ], - "outputs": { - "firstDataScienceVmUrl": { "type": "string", "value": "[concat('https://ms.portal.azure.com/#resource/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Compute/virtualMachines/', variables('vmName'), '0')]" }, - "numInstances": { "type": "string", "value": "[parameters('numberOfInstances')]" } - } -} diff --git a/inst/templates/ubuntu_dsvm_cl_ext.json b/inst/templates/ubuntu_dsvm_cl_ext.json deleted file mode 100644 index 5eea996..0000000 --- a/inst/templates/ubuntu_dsvm_cl_ext.json +++ /dev/null @@ -1,263 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "adminUsername": { - "type": "string", - "metadata": { - "description": "Username for the Virtual Machine." - } - }, - "adminPassword": { - "type": "securestring", - "metadata": { - "description": "Password for the Virtual Machine." - } - }, - "numberOfInstances": { - "type": "string", - "defaultValue": "1", - "metadata": { - "description": "Number of VMs to deploy." - } - }, - "vmName": { - "type": "string", - "metadata": { - "description": "Name Prefix for the Virtual Machine." - } - }, - "vmSize": { - "type": "string", - "defaultValue": "Standard_DS3_v2", - "metadata": { - "description": "Size for the Virtual Machine." - } - }, - "fileUris": { - "type": "string", - "metadata": { - "description": "URL of the extension package or file" - } - }, - "commandToExecute": { - "type": "string", - "defaultValue": "install.sh", - "metadata": { - "description": "Extension bash script command to execute" - } - } - }, - "variables": { - "location": "[resourceGroup().location]", - "numberOfInstances": "[int(parameters('numberOfInstances'))]", - "imagePublisher": "microsoft-dsvm", - "imageOffer": "linux-data-science-vm-ubuntu", - "sku": "linuxdsvmubuntu", - "nicName": "[parameters('vmName')]", - "addressPrefix": "10.0.0.0/16", - "subnetName": "Subnet", - "subnetPrefix": "10.0.0.0/24", - "publicIPAddressType": "Dynamic", - "publicIPAddressName": "[parameters('vmName')]", - "vmName": "[parameters('vmName')]", - "vmSize": "[parameters('vmSize')]", - "virtualNetworkName": "[parameters('vmName')]", - "nsgName": "[concat(parameters('vmName'),'-nsg')]", - "nsgId": "[resourceId(resourceGroup().name, 'Microsoft.Network/networkSecurityGroups', variables('nsgName'))]", - "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]", - "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", - "fileUris": "[parameters('fileUris')]", - "commandToExecute": "[concat('bash ', parameters('commandToExecute'))]" - }, - "resources": [ - { - "apiVersion": "2018-02-01", - "type": "Microsoft.Network/networkSecurityGroups", - "location": "[variables('location')]", - "name": "[variables('nsgName')]", - "properties": { - "securityRules": [ - { - "name": "Allow-SSH-Jupyterhub", - "properties": { - "protocol": "Tcp", - "sourcePortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*", - "access": "Allow", - "priority": 100, - "direction": "Inbound", - "sourcePortRanges": [], - "destinationPortRanges": [ - "22", - "8000" - ], - "destinationPortRange": "" - } - } - ] - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/publicIPAddresses", - "name": "[concat(variables('publicIPAddressName'), copyindex())]", - "location": "[variables('location')]", - "copy": { - "name": "publicIPLoop", - "count": "[variables('numberOfInstances')]" - }, - "properties": { - "publicIPAllocationMethod": "[variables('publicIPAddressType')]", - "dnsSettings": { - "domainNameLabel": "[concat(variables('publicIPAddressName'), copyindex())]" - } - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/virtualNetworks", - "name": "[variables('virtualNetworkName')]", - "location": "[variables('location')]", - "dependsOn": [ - "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]" - ], - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[variables('addressPrefix')]" - ] - }, - "subnets": [ - { - "name": "[variables('subnetName')]", - "properties": { - "addressPrefix": "[variables('subnetPrefix')]" - } - } - ] - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/networkInterfaces", - "name": "[concat(variables('nicName'), copyindex())]", - "location": "[variables('location')]", - "copy": { - "name": "nicLoop", - "count": "[variables('numberOfInstances')]" - }, - "dependsOn": [ - "[concat('Microsoft.Network/publicIPAddresses/', concat(variables('publicIPAddressName'),copyindex()))]", - "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" - ], - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "privateIPAllocationMethod": "Dynamic", - "publicIPAddress": { - "id": "[resourceId('Microsoft.Network/publicIPAddresses',concat(variables('publicIPAddressName'),copyindex()))]" - }, - "subnet": { - "id": "[variables('subnetRef')]" - } - } - } - ], - "networkSecurityGroup": { - "id": "[variables('nsgId')]" - } - } - }, - { - "apiVersion": "2017-03-30", - "type": "Microsoft.Compute/virtualMachines", - "name": "[concat(variables('vmName'), copyIndex())]", - "location": "[variables('location')]", - "copy": { - "name": "virtualMachineLoop", - "count": "[variables('numberOfInstances')]" - }, - "tags": { - "Application": "DataScience" - }, - "dependsOn": [ - "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'), copyindex())]" - ], - "properties": { - "hardwareProfile": { - "vmSize": "[variables('vmSize')]" - }, - "osProfile": { - "computerName": "[concat(variables('vmName'), copyIndex())]", - "adminUsername": "[parameters('adminUsername')]", - "adminPassword": "[parameters('adminPassword')]" - }, - "storageProfile": { - "imageReference": { - "publisher": "[variables('imagePublisher')]", - "offer": "[variables('imageOffer')]", - "sku": "[variables('sku')]", - "version": "latest" - }, - "osDisk": { - "managedDisk": { - "storageAccountType": "Standard_LRS" - }, - "createOption": "FromImage" - }, - "dataDisks": [ - { - "managedDisk": { - "storageAccountType": "Standard_LRS" - }, - "createOption": "FromImage", - "lun": 0 - } - ] - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces',concat(variables('nicName'), copyindex()))]" - } - ] - }, - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": false - } - } - }, - "resources": [ - { - "type": "extensions", - "name": "[variables('vmName')]", - "apiVersion": "2017-03-30", - "location": "[variables('location')]", - "dependsOn": [ - "[concat('Microsoft.Compute/virtualMachines/', variables('vmName'),copyindex())]" - ], - "properties": { - "publisher": "Microsoft.OSTCExtensions", - "type": "CustomScriptForLinux", - "typeHandlerVersion": "1.4", - "autoUpgradeMinorVersion": true, - "settings": { - "fileUris": "[split(variables('fileUris'), ' ')]", - "commandToExecute": "[variables('commandToExecute')]" - } - } - } - ] - } - ], - "outputs": { - "firstDataScienceVmUrl": { "type": "string", "value": "[concat('https://ms.portal.azure.com/#resource/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Compute/virtualMachines/', variables('vmName'), '0')]" }, - "numInstances": { "type": "string", "value": "[parameters('numberOfInstances')]" } - } -} diff --git a/inst/templates/ubuntu_dsvm_cl_key.json b/inst/templates/ubuntu_dsvm_cl_key.json deleted file mode 100644 index 1bd3da1..0000000 --- a/inst/templates/ubuntu_dsvm_cl_key.json +++ /dev/null @@ -1,244 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "adminUsername": { - "type": "string", - "metadata": { - "description": "Username for the Virtual Machine." - } - }, - "sshKeyData": { - "type": "string", - "metadata": { - "description": "SSH public key data as a string." - } - }, - "numberOfInstances": { - "type": "string", - "defaultValue": "1", - "metadata": { - "description": "Number of VMs to deploy." - } - }, - "vmName": { - "type": "string", - "metadata": { - "description": "Name Prefix for the Virtual Machine." - } - }, - "vmSize": { - "type": "string", - "defaultValue": "Standard_DS3_v2", - "metadata": { - "description": "Size for the Virtual Machine." - } - } - }, - "variables": { - "location": "[resourceGroup().location]", - "numberOfInstances": "[int(parameters('numberOfInstances'))]", - "imagePublisher": "microsoft-dsvm", - "imageOffer": "linux-data-science-vm-ubuntu", - "sku": "linuxdsvmubuntu", - "nicName": "[parameters('vmName')]", - "addressPrefix": "10.0.0.0/16", - "subnetName": "Subnet", - "subnetPrefix": "10.0.0.0/24", - "publicIPAddressType": "Dynamic", - "publicIPAddressName": "[parameters('vmName')]", - "vmName": "[parameters('vmName')]", - "vmSize": "[parameters('vmSize')]", - "virtualNetworkName": "[parameters('vmName')]", - "nsgName": "[concat(parameters('vmName'),'-nsg')]", - "nsgId": "[resourceId(resourceGroup().name, 'Microsoft.Network/networkSecurityGroups', variables('nsgName'))]", - "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]", - "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", - "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]" - }, - "resources": [ - { - "apiVersion": "2018-02-01", - "type": "Microsoft.Network/networkSecurityGroups", - "location": "[variables('location')]", - "name": "[variables('nsgName')]", - "properties": { - "securityRules": [ - { - "name": "Allow-SSH-Jupyterhub", - "properties": { - "protocol": "Tcp", - "sourcePortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*", - "access": "Allow", - "priority": 100, - "direction": "Inbound", - "sourcePortRanges": [], - "destinationPortRanges": [ - "22", - "8000" - ], - "destinationPortRange": "" - } - } - ] - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/publicIPAddresses", - "name": "[concat(variables('publicIPAddressName'), copyindex())]", - "location": "[variables('location')]", - "copy": { - "name": "publicIPLoop", - "count": "[variables('numberOfInstances')]" - }, - "properties": { - "publicIPAllocationMethod": "[variables('publicIPAddressType')]", - "dnsSettings": { - "domainNameLabel": "[concat(variables('publicIPAddressName'), copyindex())]" - } - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/virtualNetworks", - "name": "[variables('virtualNetworkName')]", - "location": "[variables('location')]", - "dependsOn": [ - "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]" - ], - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[variables('addressPrefix')]" - ] - }, - "subnets": [ - { - "name": "[variables('subnetName')]", - "properties": { - "addressPrefix": "[variables('subnetPrefix')]" - } - } - ] - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/networkInterfaces", - "name": "[concat(variables('nicName'), copyindex())]", - "location": "[variables('location')]", - "copy": { - "name": "nicLoop", - "count": "[variables('numberOfInstances')]" - }, - "dependsOn": [ - "[concat('Microsoft.Network/publicIPAddresses/', concat(variables('publicIPAddressName'),copyindex()))]", - "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" - ], - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "privateIPAllocationMethod": "Dynamic", - "publicIPAddress": { - "id": "[resourceId('Microsoft.Network/publicIPAddresses',concat(variables('publicIPAddressName'),copyindex()))]" - }, - "subnet": { - "id": "[variables('subnetRef')]" - } - } - } - ], - "networkSecurityGroup": { - "id": "[variables('nsgId')]" - } - } - }, - { - "apiVersion": "2017-03-30", - "type": "Microsoft.Compute/virtualMachines", - "name": "[concat(variables('vmName'), copyIndex())]", - "location": "[variables('location')]", - "copy": { - "name": "virtualMachineLoop", - "count": "[variables('numberOfInstances')]" - }, - "tags": { - "Application": "DataScience" - }, - "dependsOn": [ - "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'), copyindex())]" - ], - "properties": { - "hardwareProfile": { - "vmSize": "[variables('vmSize')]" - }, - "osProfile": { - "computerName": "[concat(variables('vmName'), copyIndex())]", - "adminUsername": "[parameters('adminUsername')]", - "linuxConfiguration": { - "disablePasswordAuthentication": true, - "ssh": { - "publicKeys": [ - { - "path": "[variables('sshKeyPath')]", - "keyData": "[parameters('sshKeyData')]" - } - ] - } - } - }, - "storageProfile": { - "imageReference": { - "publisher": "[variables('imagePublisher')]", - "offer": "[variables('imageOffer')]", - "sku": "[variables('sku')]", - "version": "latest" - }, - "osDisk": { - "managedDisk": { - "storageAccountType": "Standard_LRS" - }, - "createOption": "FromImage" - }, - "dataDisks": [ - { - "managedDisk": { - "storageAccountType": "Standard_LRS" - }, - "createOption": "FromImage", - "lun": 0 - } - ] - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces',concat(variables('nicName'), copyindex()))]" - } - ] - }, - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": false - } - } - } - } - ], - "outputs": { - "firstDataScienceVmUrl": { - "type": "string", - "value": "[concat('https://ms.portal.azure.com/#resource/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Compute/virtualMachines/', variables('vmName'), '0')]" - }, - "numInstances": { - "type": "string", - "value": "[parameters('numberOfInstances')]" - } - } -} diff --git a/inst/templates/ubuntu_dsvm_ext.json b/inst/templates/ubuntu_dsvm_ext.json deleted file mode 100644 index 330cbb1..0000000 --- a/inst/templates/ubuntu_dsvm_ext.json +++ /dev/null @@ -1,252 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "adminUsername": { - "type": "string", - "metadata": { - "description": "Username for the Virtual Machine." - } - }, - "adminPassword": { - "type": "securestring", - "metadata": { - "description": "Password for the Virtual Machine." - } - }, - "vmName": { - "type": "string", - "metadata": { - "description": "Name Prefix for the Virtual Machine." - } - }, - "vmSize": { - "type": "string", - "defaultValue": "Standard_DS3_v2", - "metadata": { - "description": "Size for the Virtual Machine." - } - }, - "fileUris": { - "type": "string", - "metadata": { - "description": "URL of the extension package or file" - } - }, - "commandToExecute": { - "type": "string", - "defaultValue": "install.sh", - "metadata": { - "description": "Extension bash script command to execute" - } - }, - "commandParameter": { - "type": "string", - "metadata": { - "description": "Extension bash script command parameter" - } - } - }, - "variables": { - "location": "[resourceGroup().location]", - "imagePublisher": "microsoft-dsvm", - "imageOffer": "linux-data-science-vm-ubuntu", - "OSDiskName": "[concat(parameters('vmName'),'-osdisk')]", - "DataDiskName": "[concat(parameters('vmName'),'-data-0')]", - "sku": "linuxdsvmubuntu", - "nicName": "[parameters('vmName')]", - "addressPrefix": "10.0.0.0/16", - "subnetName": "Subnet", - "subnetPrefix": "10.0.0.0/24", - "publicIPAddressType": "Dynamic", - "publicIPAddressName": "[parameters('vmName')]", - "vmName": "[parameters('vmName')]", - "vmSize": "[parameters('vmSize')]", - "virtualNetworkName": "[parameters('vmName')]", - "nsgName": "[concat(parameters('vmName'),'-nsg')]", - "nsgId": "[resourceId(resourceGroup().name, 'Microsoft.Network/networkSecurityGroups', variables('nsgName'))]", - "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]", - "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", - "fileUris": "[parameters('fileUris')]", - "commandToExecute": "[concat('bash ', parameters('commandToExecute'), ' ', parameters('commandParameter'))]" - }, - "resources": [ - { - "apiVersion": "2018-02-01", - "type": "Microsoft.Network/networkSecurityGroups", - "location": "[variables('location')]", - "name": "[variables('nsgName')]", - "properties": { - "securityRules": [ - { - "name": "Allow-SSH-Jupyterhub", - "properties": { - "protocol": "Tcp", - "sourcePortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*", - "access": "Allow", - "priority": 100, - "direction": "Inbound", - "sourcePortRanges": [], - "destinationPortRanges": [ - "22", - "8000" - ], - "destinationPortRange": "" - } - } - ] - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/publicIPAddresses", - "name": "[variables('publicIPAddressName')]", - "location": "[variables('location')]", - "properties": { - "publicIPAllocationMethod": "[variables('publicIPAddressType')]", - "dnsSettings": { - "domainNameLabel": "[variables('publicIPAddressName')]" - } - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/virtualNetworks", - "name": "[variables('virtualNetworkName')]", - "location": "[variables('location')]", - "dependsOn": [ - "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]" - ], - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[variables('addressPrefix')]" - ] - }, - "subnets": [ - { - "name": "[variables('subnetName')]", - "properties": { - "addressPrefix": "[variables('subnetPrefix')]" - } - } - ] - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/networkInterfaces", - "name": "[variables('nicName')]", - "location": "[variables('location')]", - "dependsOn": [ - "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]", - "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" - ], - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "privateIPAllocationMethod": "Dynamic", - "publicIPAddress": { - "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]" - }, - "subnet": { - "id": "[variables('subnetRef')]" - } - } - } - ], - "networkSecurityGroup": { - "id": "[variables('nsgId')]" - } - } - }, - { - "apiVersion": "2017-03-30", - "type": "Microsoft.Compute/virtualMachines", - "name": "[variables('vmName')]", - "location": "[variables('location')]", - "tags": { - "Application": "DataScience" - }, - "dependsOn": [ - "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]" - ], - "properties": { - "hardwareProfile": { - "vmSize": "[variables('vmSize')]" - }, - "osProfile": { - "computerName": "[variables('vmName')]", - "adminUsername": "[parameters('adminUsername')]", - "adminPassword": "[parameters('adminPassword')]" - }, - "storageProfile": { - "imageReference": { - "publisher": "[variables('imagePublisher')]", - "offer": "[variables('imageOffer')]", - "sku": "[variables('sku')]", - "version": "latest" - }, - "osDisk": { - "name": "[variables('OSDiskName')]", - "managedDisk": { - "storageAccountType": "Standard_LRS" - }, - "createOption": "FromImage" - }, - "dataDisks": [ - { - "name": "[variables('DataDiskName')]", - "managedDisk": { - "storageAccountType": "Standard_LRS" - }, - "createOption": "FromImage", - "lun": 0 - } - ] - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]" - } - ] - }, - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": false - } - } - }, - "resources": [ - { - "type": "extensions", - "name": "[variables('vmName')]", - "apiVersion": "2017-03-30", - "location": "[variables('location')]", - "dependsOn": [ - "[concat('Microsoft.Compute/virtualMachines/', variables('vmName')]" - ], - "properties": { - "publisher": "Microsoft.OSTCExtensions", - "type": "CustomScriptForLinux", - "typeHandlerVersion": "1.4", - "autoUpgradeMinorVersion": true, - "settings": { - "fileUris": "[split(variables('fileUris'), ' ')]", - "commandToExecute": "[variables('commandToExecute')]" - } - } - } - ] - } - ], - "outputs": { - "DataScienceVmUrl": { "type": "string", "value": "[concat('https://ms.portal.azure.com/#resource/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Compute/virtualMachines/', variables('vmName'))]" } - } -} diff --git a/inst/templates/ubuntu_dsvm_key.json b/inst/templates/ubuntu_dsvm_key.json deleted file mode 100644 index 497d8ac..0000000 --- a/inst/templates/ubuntu_dsvm_key.json +++ /dev/null @@ -1,221 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "adminUsername": { - "type": "string", - "metadata": { - "description": "Username for the Virtual Machine." - } - }, - "sshKeyData": { - "type": "string", - "metadata": { - "description": "SSH public key data as a string." - } - }, - "vmName": { - "type": "string", - "metadata": { - "description": "Name Prefix for the Virtual Machine." - } - }, - "vmSize": { - "type": "string", - "defaultValue": "Standard_DS3_v2", - "metadata": { - "description": "Size for the Virtual Machine." - } - } - }, - "variables": { - "location": "[resourceGroup().location]", - "imagePublisher": "microsoft-dsvm", - "imageOffer": "linux-data-science-vm-ubuntu", - "OSDiskName": "[concat(parameters('vmName'),'-osdisk')]", - "DataDiskName": "[concat(parameters('vmName'),'-data-0')]", - "sku": "linuxdsvmubuntu", - "nicName": "[parameters('vmName')]", - "addressPrefix": "10.0.0.0/16", - "subnetName": "Subnet", - "subnetPrefix": "10.0.0.0/24", - "publicIPAddressType": "Dynamic", - "publicIPAddressName": "[parameters('vmName')]", - "vmName": "[parameters('vmName')]", - "vmSize": "[parameters('vmSize')]", - "virtualNetworkName": "[parameters('vmName')]", - "nsgName": "[concat(parameters('vmName'),'-nsg')]", - "nsgId": "[resourceId(resourceGroup().name, 'Microsoft.Network/networkSecurityGroups', variables('nsgName'))]", - "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]", - "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", - "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]" - }, - "resources": [ - { - "apiVersion": "2018-02-01", - "type": "Microsoft.Network/networkSecurityGroups", - "location": "[variables('location')]", - "name": "[variables('nsgName')]", - "properties": { - "securityRules": [ - { - "name": "Allow-SSH-Jupyterhub", - "properties": { - "protocol": "Tcp", - "sourcePortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*", - "access": "Allow", - "priority": 100, - "direction": "Inbound", - "sourcePortRanges": [], - "destinationPortRanges": [ - "22", - "8000" - ], - "destinationPortRange": "" - } - } - ] - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/publicIPAddresses", - "name": "[variables('publicIPAddressName')]", - "location": "[variables('location')]", - "properties": { - "publicIPAllocationMethod": "[variables('publicIPAddressType')]", - "dnsSettings": { - "domainNameLabel": "[variables('publicIPAddressName')]" - } - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/virtualNetworks", - "name": "[variables('virtualNetworkName')]", - "location": "[variables('location')]", - "dependsOn": [ - "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]" - ], - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[variables('addressPrefix')]" - ] - }, - "subnets": [ - { - "name": "[variables('subnetName')]", - "properties": { - "addressPrefix": "[variables('subnetPrefix')]" - } - } - ] - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/networkInterfaces", - "name": "[variables('nicName')]", - "location": "[variables('location')]", - "dependsOn": [ - "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]", - "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" - ], - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "privateIPAllocationMethod": "Dynamic", - "publicIPAddress": { - "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]" - }, - "subnet": { - "id": "[variables('subnetRef')]" - } - } - } - ], - "networkSecurityGroup": { - "id": "[variables('nsgId')]" - } - } - }, - { - "apiVersion": "2017-03-30", - "type": "Microsoft.Compute/virtualMachines", - "name": "[variables('vmName')]", - "location": "[variables('location')]", - "tags": { - "Application": "DataScience" - }, - "dependsOn": [ - "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]" - ], - "properties": { - "hardwareProfile": { - "vmSize": "[variables('vmSize')]" - }, - "osProfile": { - "computerName": "[variables('vmName')]", - "adminUsername": "[parameters('adminUsername')]", - "linuxConfiguration": { - "disablePasswordAuthentication": true, - "ssh": { - "publicKeys": [ - { - "path": "[variables('sshKeyPath')]", - "keyData": "[parameters('sshKeyData')]" - } - ] - } - } - }, - "storageProfile": { - "imageReference": { - "publisher": "[variables('imagePublisher')]", - "offer": "[variables('imageOffer')]", - "sku": "[variables('sku')]", - "version": "latest" - }, - "osDisk": { - "name": "[variables('OSDiskName')]", - "managedDisk": { - "storageAccountType": "Standard_LRS" - }, - "createOption": "FromImage" - }, - "dataDisks": [ - { - "name": "[variables('DataDiskName')]", - "managedDisk": { - "storageAccountType": "Standard_LRS" - }, - "createOption": "FromImage", - "lun": 0 - } - ] - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]" - } - ] - }, - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": false - } - } - } - } - ], - "outputs": { - "DataScienceVmUrl": { "type": "string", "value": "[concat('https://ms.portal.azure.com/#resource/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Compute/virtualMachines/', variables('vmName'))]" } - } -} diff --git a/inst/templates/win2016_dsvm.json b/inst/templates/win2016_dsvm.json deleted file mode 100644 index cf0fc46..0000000 --- a/inst/templates/win2016_dsvm.json +++ /dev/null @@ -1,198 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "adminUsername": { - "type": "string", - "metadata": { - "description": "Username for the Virtual Machine." - } - }, - "adminPassword": { - "type": "securestring", - "metadata": { - "description": "Password for the Virtual Machine." - } - }, - "vmName": { - "type": "string", - "metadata": { - "description": "Name for the Virtual Machine." - } - }, - "vmSize": { - "type": "string", - "defaultValue": "Standard_DS3_v2", - "metadata": { - "description": "Size for the Virtual Machine." - } - } - }, - "variables": { - "location": "[resourceGroup().location]", - "imagePublisher": "microsoft-dsvm", - "imageOffer": "dsvm-windows", - "sku": "server-2016", - "nicName": "[parameters('vmName')]", - "addressPrefix": "10.0.0.0/16", - "subnetName": "Subnet", - "subnetPrefix": "10.0.0.0/24", - "storageAccountType": "Standard_LRS", - "publicIPAddressType": "Dynamic", - "publicIPAddressName": "[parameters('vmName')]", - "vmStorageAccountContainerName": "vhds", - "vmName": "[parameters('vmName')]", - "vmSize": "[parameters('vmSize')]", - "virtualNetworkName": "[parameters('vmName')]", - "nsgName": "[concat(parameters('vmName'),'-nsg')]", - "nsgId": "[resourceId(resourceGroup().name, 'Microsoft.Network/networkSecurityGroups', variables('nsgName'))]", - "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]", - "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]" - }, - "resources": [ - { - "apiVersion": "2018-02-01", - "type": "Microsoft.Network/networkSecurityGroups", - "location": "[variables('location')]", - "name": "[variables('nsgName')]", - "properties": { - "securityRules": [ - { - "name": "Allow-RDP", - "properties": { - "protocol": "Tcp", - "sourcePortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*", - "access": "Allow", - "priority": 100, - "direction": "Inbound", - "sourcePortRanges": [], - "destinationPortRanges": [ - "3389" - ], - "destinationPortRange": "" - } - } - ] - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/publicIPAddresses", - "name": "[variables('publicIPAddressName')]", - "location": "[variables('location')]", - "properties": { - "publicIPAllocationMethod": "[variables('publicIPAddressType')]", - "dnsSettings": { - "domainNameLabel": "[variables('publicIPAddressName')]" - } - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/virtualNetworks", - "name": "[variables('virtualNetworkName')]", - "location": "[variables('location')]", - "dependsOn": [ - "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]" - ], - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[variables('addressPrefix')]" - ] - }, - "subnets": [ - { - "name": "[variables('subnetName')]", - "properties": { - "addressPrefix": "[variables('subnetPrefix')]" - } - } - ] - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/networkInterfaces", - "name": "[variables('nicName')]", - "location": "[variables('location')]", - "dependsOn": [ - "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]", - "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" - ], - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "privateIPAllocationMethod": "Dynamic", - "publicIPAddress": { - "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]" - }, - "subnet": { - "id": "[variables('subnetRef')]" - } - } - } - ], - "networkSecurityGroup": { - "id": "[variables('nsgId')]" - } - } - }, - { - "apiVersion": "2017-03-30", - "type": "Microsoft.Compute/virtualMachines", - "name": "[variables('vmName')]", - "location": "[variables('location')]", - "tags": { - "Application": "DataScience" - }, - "dependsOn": [ - "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]" - ], - "properties": { - "hardwareProfile": { - "vmSize": "[variables('vmSize')]" - }, - "osProfile": { - "computerName": "[variables('vmName')]", - "adminUsername": "[parameters('adminUsername')]", - "adminPassword": "[parameters('adminPassword')]" - }, - "storageProfile": { - "imageReference": { - "publisher": "[variables('imagePublisher')]", - "offer": "[variables('imageOffer')]", - "sku": "[variables('sku')]", - "version": "latest" - }, - "osDisk": { - "managedDisk": { - "storageAccountType": "[variables('storageAccountType')]" - }, - "createOption": "FromImage" - } - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]" - } - ] - }, - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": false - } - } - } - } - ], - "outputs": { - "DataScienceVmUrl": { "type": "string", "value": "[concat('https://ms.portal.azure.com/#resource/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Compute/virtualMachines/', variables('vmName'))]" } - } -} diff --git a/inst/templates/win2016_dsvm_cl_ext.json b/inst/templates/win2016_dsvm_cl_ext.json deleted file mode 100644 index c7ee3c7..0000000 --- a/inst/templates/win2016_dsvm_cl_ext.json +++ /dev/null @@ -1,254 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "adminUsername": { - "type": "string", - "metadata": { - "description": "Username for the Virtual Machine." - } - }, - "adminPassword": { - "type": "securestring", - "metadata": { - "description": "Password for the Virtual Machine." - } - }, - "numberOfInstances": { - "type": "string", - "defaultValue": "1", - "metadata": { - "description": "Number of VMs to deploy." - } - }, - "vmName": { - "type": "string", - "metadata": { - "description": "Name Prefix for the Virtual Machine." - } - }, - "vmSize": { - "type": "string", - "defaultValue": "Standard_DS3_v2", - "metadata": { - "description": "Size for the Virtual Machine." - } - }, - "fileUris": { - "type": "string", - "metadata": { - "description": "URL of the extension package or file" - } - }, - "commandToExecute": { - "type": "string", - "defaultValue": "install.ps1", - "metadata": { - "description": "Extension powershell script command to execute" - } - } - }, - "variables": { - "location": "[resourceGroup().location]", - "numberOfInstances": "[int(parameters('numberOfInstances'))]", - "imagePublisher": "microsoft-dsvm", - "imageOffer": "dsvm-windows", - "sku": "server-2016", - "nicName": "[parameters('vmName')]", - "addressPrefix": "10.0.0.0/16", - "subnetName": "Subnet", - "subnetPrefix": "10.0.0.0/24", - "storageAccountType": "Standard_LRS", - "publicIPAddressType": "Dynamic", - "publicIPAddressName": "[parameters('vmName')]", - "vmName": "[parameters('vmName')]", - "vmSize": "[parameters('vmSize')]", - "virtualNetworkName": "[parameters('vmName')]", - "nsgName": "[concat(parameters('vmName'),'-nsg')]", - "nsgId": "[resourceId(resourceGroup().name, 'Microsoft.Network/networkSecurityGroups', variables('nsgName'))]", - "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]", - "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", - "fileUris": "[parameters('fileUris')]", - "commandToExecute": "[concat('powershell.exe -ExecutionPolicy Unrestricted -File ', parameters('commandToExecute'))]" - }, - "resources": [ - { - "apiVersion": "2018-02-01", - "type": "Microsoft.Network/networkSecurityGroups", - "location": "[variables('location')]", - "name": "[variables('nsgName')]", - "properties": { - "securityRules": [ - { - "name": "Allow-RDP", - "properties": { - "protocol": "Tcp", - "sourcePortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*", - "access": "Allow", - "priority": 100, - "direction": "Inbound", - "sourcePortRanges": [], - "destinationPortRanges": [ - "3389" - ], - "destinationPortRange": "" - } - } - ] - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/publicIPAddresses", - "name": "[concat(variables('publicIPAddressName'), copyindex())]", - "location": "[variables('location')]", - "copy": { - "name": "publicIPLoop", - "count": "[variables('numberOfInstances')]" - }, - "properties": { - "publicIPAllocationMethod": "[variables('publicIPAddressType')]", - "dnsSettings": { - "domainNameLabel": "[concat(variables('publicIPAddressName'), copyindex())]" - } - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/virtualNetworks", - "name": "[variables('virtualNetworkName')]", - "location": "[variables('location')]", - "dependsOn": [ - "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]" - ], - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[variables('addressPrefix')]" - ] - }, - "subnets": [ - { - "name": "[variables('subnetName')]", - "properties": { - "addressPrefix": "[variables('subnetPrefix')]" - } - } - ] - } - }, - { - "apiVersion": "2015-05-01-preview", - "type": "Microsoft.Network/networkInterfaces", - "name": "[concat(variables('nicName'), copyindex())]", - "location": "[variables('location')]", - "copy": { - "name": "nicLoop", - "count": "[variables('numberOfInstances')]" - }, - "dependsOn": [ - "[concat('Microsoft.Network/publicIPAddresses/', concat(variables('publicIPAddressName'),copyindex()))]", - "[concat('Microsoft.Network/networkSecurityGroups/', variables('nsgName'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" - ], - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "privateIPAllocationMethod": "Dynamic", - "publicIPAddress": { - "id": "[resourceId('Microsoft.Network/publicIPAddresses',concat(variables('publicIPAddressName'),copyindex()))]" - }, - "subnet": { - "id": "[variables('subnetRef')]" - } - } - } - ], - "networkSecurityGroup": { - "id": "[variables('nsgId')]" - } - } - }, - { - "apiVersion": "2017-03-30", - "type": "Microsoft.Compute/virtualMachines", - "name": "[concat(variables('vmName'), copyIndex())]", - "location": "[variables('location')]", - "copy": { - "name": "virtualMachineLoop", - "count": "[variables('numberOfInstances')]" - }, - "tags": { - "Application": "DataScience" - }, - "dependsOn": [ - "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'), copyindex())]" - ], - "properties": { - "hardwareProfile": { - "vmSize": "[variables('vmSize')]" - }, - "osProfile": { - "computerName": "[concat(variables('vmName'), copyIndex())]", - "adminUsername": "[parameters('adminUsername')]", - "adminPassword": "[parameters('adminPassword')]" - }, - "storageProfile": { - "imageReference": { - "publisher": "[variables('imagePublisher')]", - "offer": "[variables('imageOffer')]", - "sku": "[variables('sku')]", - "version": "latest" - }, - "osDisk": { - "managedDisk": { - "storageAccountType": "[variables('storageAccountType')]" - }, - "createOption": "FromImage" - } - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces',concat(variables('nicName'), copyindex()))]" - } - ] - }, - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": false - } - } - }, - "resources": [ - { - "type": "extensions", - "name": "[variables('vmName')]", - "apiVersion": "2017-03-30", - "location": "[variables('location')]", - "dependsOn": [ - "[concat('Microsoft.Compute/virtualMachines/', variables('vmName'),copyindex())]" - ], - "properties": { - "publisher": "Microsoft.Compute", - "type": "CustomScriptExtension", - "typeHandlerVersion": "1.8", - "autoUpgradeMinorVersion": true, - "settings": { - "fileUris": "[split(variables('fileUris'), ' ')]", - "commandToExecute": "[variables('commandToExecute')]" - } - } - } - ] - } - ], - "outputs": { - "firstDataScienceVmUrl": { "type": "string", "value": "[concat('https://ms.portal.azure.com/#resource/subscriptions/', subscription().subscriptionId, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Compute/virtualMachines/', variables('vmName'), '0')]" }, - "numInstances": { "type": "string", "value": "[parameters('numberOfInstances')]" } - } -} \ No newline at end of file diff --git a/inst/tpl/as_default.json b/inst/tpl/as_default.json new file mode 100644 index 0000000..10b720a --- /dev/null +++ b/inst/tpl/as_default.json @@ -0,0 +1,14 @@ +{ + "apiVersion": "2015-04-01", + "type": "Microsoft.Insights/autoscaleSettings", + "name": "[variables('asName')]", + "location": "[variables('location')]", + "dependsOn": [ + "[variables('vmRef')]" + ], + "properties": { + "name": "[variables('asName')]", + "targetResourceUri": "[variables('vmId')]", + "enabled": true + } +} diff --git a/inst/tpl/disk_default.json b/inst/tpl/disk_default.json new file mode 100644 index 0000000..2d383dd --- /dev/null +++ b/inst/tpl/disk_default.json @@ -0,0 +1,17 @@ +{ + "apiVersion": "2018-09-30", + "name": "[parameters('dataDiskResources')[copyIndex()].name]", + "type": "Microsoft.Compute/disks", + "location": "[variables('location')]", + "properties": { + "diskSizeGB": "[parameters('dataDiskResources')[copyIndex()].diskSizeGB]", + "creationData": "[parameters('dataDiskResources')[copyIndex()].creationData]" + }, + "sku": { + "name": "[parameters('dataDiskResources')[copyIndex()].sku]" + }, + "copy": { + "name": "managedDiskResources", + "count": "[length(parameters('dataDiskResources'))]" + } +} diff --git a/inst/tpl/ip_default.json b/inst/tpl/ip_default.json new file mode 100644 index 0000000..d6e5249 --- /dev/null +++ b/inst/tpl/ip_default.json @@ -0,0 +1,10 @@ +{ + "apiVersion": "2018-11-01", + "type": "Microsoft.Network/publicIPAddresses", + "name": "[variables('ipName')]", + "location": "[variables('location')]", + "properties": {}, + "sku": { + "name": "Basic" + } +} diff --git a/inst/tpl/lb_default.json b/inst/tpl/lb_default.json new file mode 100644 index 0000000..885909e --- /dev/null +++ b/inst/tpl/lb_default.json @@ -0,0 +1,29 @@ +{ + "apiVersion": "2018-11-01", + "type": "Microsoft.Network/loadBalancers", + "name": "[variables('lbName')]", + "location": "[variables('location')]", + "dependsOn": [ + "[variables('ipRef')]" + ], + "sku": { + "name": "basic" + }, + "properties": { + "frontendIPConfigurations": [ + { + "name": "[variables('lbFrontendName')]", + "properties": { + "publicIPAddress": { + "id": "[variables('ipId')]" + } + } + } + ], + "backendAddressPools": [ + { + "name": "[variables('lbBackendName')]" + } + ] + } +} diff --git a/inst/tpl/nic_default.json b/inst/tpl/nic_default.json new file mode 100644 index 0000000..7757e5e --- /dev/null +++ b/inst/tpl/nic_default.json @@ -0,0 +1,11 @@ +{ + "apiVersion": "2018-11-01", + "type": "Microsoft.Network/networkInterfaces", + "name": "[variables('nicName')]", + "location": "[variables('location')]", + "dependsOn": [ + "[variables('ipRef')]", + "[variables('vnetRef')]" + ], + "properties": {} +} diff --git a/inst/tpl/nsg_default.json b/inst/tpl/nsg_default.json new file mode 100644 index 0000000..1fa5e8f --- /dev/null +++ b/inst/tpl/nsg_default.json @@ -0,0 +1,9 @@ +{ + "apiVersion": "2018-11-01", + "type": "Microsoft.Network/networkSecurityGroups", + "name": "[variables('nsgName')]", + "location": "[variables('location')]", + "properties": { + "securityRules": [] + } +} diff --git a/inst/tpl/sstpl_parameters_default.json b/inst/tpl/sstpl_parameters_default.json new file mode 100644 index 0000000..d0ac02c --- /dev/null +++ b/inst/tpl/sstpl_parameters_default.json @@ -0,0 +1,32 @@ +{ + "vmName": { + "type": "string" + }, + "vmSize": { + "type": "string" + }, + "adminUsername": { + "type": "string" + }, + "instanceCount": { + "type": "int" + }, + "priority": { + "type": "string" + }, + "evictionPolicy": { + "type": "string" + }, + "enableAcceleratedNetworking": { + "type": "bool" + }, + "singlePlacementGroup": { + "type": "bool" + }, + "overprovision":{ + "type": "bool" + }, + "upgradePolicy":{ + "type": "object" + } +} diff --git a/inst/tpl/tpl_outputs_default.json b/inst/tpl/tpl_outputs_default.json new file mode 100644 index 0000000..43cc97d --- /dev/null +++ b/inst/tpl/tpl_outputs_default.json @@ -0,0 +1,10 @@ +{ + "vmResource": { + "type": "string", + "value": "[variables('vmId')]" + }, + "adminUsername": { + "type": "string", + "value": "[parameters('adminUsername')]" + } +} diff --git a/inst/tpl/tpl_parameters_default.json b/inst/tpl/tpl_parameters_default.json new file mode 100644 index 0000000..4418e13 --- /dev/null +++ b/inst/tpl/tpl_parameters_default.json @@ -0,0 +1,11 @@ +{ + "vmName": { + "type": "string" + }, + "vmSize": { + "type": "string" + }, + "adminUsername": { + "type": "string" + } +} diff --git a/inst/tpl/vm_datadisk.json b/inst/tpl/vm_datadisk.json new file mode 100644 index 0000000..ca8eccb --- /dev/null +++ b/inst/tpl/vm_datadisk.json @@ -0,0 +1,18 @@ +[ + { + "name": "dataDisks", + "count": "[length(parameters('dataDisks'))]", + "input": { + "lun": "[parameters('dataDisks')[copyIndex('dataDisks')].lun]", + "createOption": "[parameters('dataDisks')[copyIndex('dataDisks')].createOption]", + "caching": "[parameters('dataDisks')[copyIndex('dataDisks')].caching]", + "writeAcceleratorEnabled": "[parameters('dataDisks')[copyIndex('dataDisks')].writeAcceleratorEnabled]", + "diskSizeGB": "[parameters('dataDisks')[copyIndex('dataDisks')].diskSizeGB]", + "managedDisk": { + "id": "[coalesce(parameters('dataDisks')[copyIndex('dataDisks')].id, if(equals(parameters('dataDisks')[copyIndex('dataDisks')].name, json('null')), json('null'), resourceId('Microsoft.Compute/disks', parameters('dataDisks')[copyIndex('dataDisks')].name)))]", + "storageAccountType": "[parameters('dataDisks')[copyIndex('dataDisks')].storageAccountType]" + } + } + } +] + diff --git a/inst/tpl/vm_default.json b/inst/tpl/vm_default.json new file mode 100644 index 0000000..515a8ee --- /dev/null +++ b/inst/tpl/vm_default.json @@ -0,0 +1,42 @@ +{ + "apiVersion": "2019-03-01", + "type": "Microsoft.Compute/virtualMachines", + "name": "[parameters('vmName')]", + "location": "[variables('location')]", + "tags": { + "CreatedBy": "AzureR/AzureVM" + }, + "dependsOn": [ + "[variables('nicRef')]" + ], + "properties": { + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "osProfile": { + "computerName": "[parameters('vmName')]", + "adminUsername": "[parameters('adminUsername')]" + }, + "storageProfile": { + "imageReference": { + "publisher": "[parameters('imagePublisher')]", + "offer": "[parameters('imageOffer')]", + "sku": "[parameters('imageSku')]", + "version": "[parameters('imageVersion')]" + }, + "osDisk": { + "managedDisk": { + "storageAccountType": "Premium_LRS" + }, + "createOption": "FromImage" + } + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[variables('nicId')]" + } + ] + } + } +} diff --git a/inst/tpl/vm_key_login.json b/inst/tpl/vm_key_login.json new file mode 100644 index 0000000..0fcca1c --- /dev/null +++ b/inst/tpl/vm_key_login.json @@ -0,0 +1,13 @@ +{ + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "ssh": { + "publicKeys": [ + { + "path": "[concat('/home/', parameters('adminUsername'), '/.ssh/authorized_keys')]", + "keyData": "[parameters('sshKeyData')]" + } + ] + } + } +} diff --git a/inst/tpl/vm_pwd_login.json b/inst/tpl/vm_pwd_login.json new file mode 100644 index 0000000..c9bb171 --- /dev/null +++ b/inst/tpl/vm_pwd_login.json @@ -0,0 +1,3 @@ +{ + "adminPassword": "[parameters('adminPassword')]" +} diff --git a/inst/tpl/vmss_default.json b/inst/tpl/vmss_default.json new file mode 100644 index 0000000..998e132 --- /dev/null +++ b/inst/tpl/vmss_default.json @@ -0,0 +1,54 @@ +{ + "apiVersion": "2019-03-01", + "type": "Microsoft.Compute/virtualMachineScaleSets", + "name": "[parameters('vmName')]", + "location": "[variables('location')]", + "tags": { + "CreatedBy": "AzureR/AzureVM" + }, + "dependsOn": [], + "sku": { + "name": "[parameters('vmSize')]", + "tier": "Standard", + "capacity": "[parameters('instanceCount')]" + }, + "properties": { + "overprovision": "[parameters('overprovision')]", + "upgradePolicy": "[parameters('upgradePolicy')]", + "singlePlacementGroup": "[parameters('singlePlacementGroup')]", + "virtualMachineProfile": { + "priority": "[parameters('priority')]", + "storageProfile": { + "osDisk": { + "createOption": "FromImage", + "caching": "ReadWrite" + } + }, + "osProfile": { + "computerNamePrefix": "[variables('vmPrefix')]", + "adminUsername": "[parameters('adminUsername')]" + }, + "networkProfile": { + "networkInterfaceConfigurations": [ + { + "name": "[concat(parameters('vmName'), '-nic')]", + "properties": { + "primary": "true", + "enableAcceleratedNetworking": "[parameters('enableAcceleratedNetworking')]", + "ipConfigurations": [ + { + "name": "[concat(parameters('vmName'), '-nic-ip')]", + "properties": { + "subnet": { + "id": "[variables('subnetId')]" + } + } + } + ] + } + } + ] + } + } + } +} diff --git a/inst/tpl/vnet_default.json b/inst/tpl/vnet_default.json new file mode 100644 index 0000000..75abef0 --- /dev/null +++ b/inst/tpl/vnet_default.json @@ -0,0 +1,10 @@ +{ + "apiVersion": "2018-11-01", + "type": "Microsoft.Network/virtualNetworks", + "name": "[variables('vnetName')]", + "location": "[variables('location')]", + "dependsOn": [ + "[variables('nsgRef')]" + ], + "properties": {} +} diff --git a/man/autoscaler_config.Rd b/man/autoscaler_config.Rd new file mode 100644 index 0000000..82e2d9d --- /dev/null +++ b/man/autoscaler_config.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/autoscaler_config.R +\name{autoscaler_config} +\alias{autoscaler_config} +\alias{autoscaler_profile} +\title{Autoscaler configuration} +\usage{ +autoscaler_config(profiles = list(autoscaler_profile()), ...) + +autoscaler_profile(name = "Profile", minsize = 1, maxsize = NA, + default = NA, scale_out = 0.75, scale_in = 0.25, + interval = "PT1M", window = "PT5M") +} +\arguments{ +\item{profiles}{A list of autoscaling profiles, each obtained via a call to \code{autoscaler_profile}.} + +\item{...}{Other named arguments that will be treated as resource properties.} + +\item{name}{For \code{autoscaler_profile}, a name for the profile.} + +\item{minsize, maxsize, default}{For \code{autoscaler_profile}, the minimum, maximum and default number of instances.} + +\item{scale_out, scale_in}{For \code{autoscaler_profile}, the percentage CPU at which to scale out and in, respectively.} + +\item{interval}{For \code{autoscaler_profile}, The interval between samples, in ISO 8601 format. The default is 1 minute.} + +\item{window}{For \code{autoscaler_profile}, the window width over which to compute the percentage CPU. The default is 5 minutes.} +} +\description{ +Autoscaler configuration +} +\seealso{ +\link{create_vm_scaleset}, \link{vmss_config} +} diff --git a/man/az_vm_resource.Rd b/man/az_vm_resource.Rd index a021ee0..0c30e3e 100644 --- a/man/az_vm_resource.Rd +++ b/man/az_vm_resource.Rd @@ -15,19 +15,23 @@ Class representing a virtual machine resource. In general, the methods in this c The following methods are available, in addition to those provided by the \link[AzureRMR:az_resource]{AzureRMR::az_resource} class: \itemize{ -\item \code{new(...)}: Initialize a new VM object. \item \code{start(wait=TRUE)}: Start the VM. By default, wait until the startup process is complete. \item \code{stop(deallocate=TRUE, wait=FALSE)}: Stop the VM. By default, deallocate it as well. \item \code{restart(wait=TRUE)}: Restart the VM. \item \code{run_deployed_command(command, parameters, script)}: Run a PowerShell command on the VM. \item \code{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. -\item \code{sync_vm_status()}: Update the VM status fields in this object with information from the host. -\item \code{resize(size, deallocate=FALSE, wait=FALSE)}: Resize the VM. Optionally deallocate it first (may sometimes be necessary). +\item \code{sync_vm_status()}: Check the status of the VM. +\item \code{resize(size, deallocate=FALSE, wait=FALSE)}: Resize the VM. Optionally stop and deallocate it first (may sometimes be necessary). +\item \code{get_public_ip_address(nic=1, config=1)}: Get the public IP address of the VM. Returns NA if the VM is shut down, or is not publicly accessible. +\item \code{get_private_ip_address(nic=1, config=1)}: Get the private IP address of the VM. +\item \code{add_extension(publisher, type, version, settings=list(), protected_settings=list(), key_vault_settings=list())}: Add an extension to the VM. +\item \code{do_vm_operation(...)}: Carry out an arbitrary operation on the VM resource. See the \code{do_operation} method of the \link[AzureRMR:az_resource]{AzureRMR::az_resource} class for more details. } } \seealso{ -\link[AzureRMR:az_resource]{AzureRMR::az_resource}, +\link[AzureRMR:az_resource]{AzureRMR::az_resource}, \link{get_vm_resource}, \link{az_vm_template} + \href{https://docs.microsoft.com/en-us/rest/api/compute/virtualmachines}{VM API reference} } \keyword{datasets} diff --git a/man/az_vm_template.Rd b/man/az_vm_template.Rd index a657db0..4772ce2 100644 --- a/man/az_vm_template.Rd +++ b/man/az_vm_template.Rd @@ -3,66 +3,50 @@ \docType{class} \name{az_vm_template} \alias{az_vm_template} -\title{Virtual machine cluster template class} +\title{Virtual machine template class} \format{An R6 object of class \code{az_vm_template}, inheriting from \code{AzureRMR::az_template}.} \usage{ az_vm_template } \description{ -Class representing a virtual machine template. This class keeps track of all resources that are created as part of deploying a VM or cluster of VMs, and exposes methods for managing them. In this page, "VM" refers to both a cluster of virtual machines, as well as a single virtual machine (which is treated as the special case of a cluster containing a single node). +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. } \details{ -A single virtual machine in Azure is actually a collection of resources, including any and all of the following. A cluster can share a storage account and virtual network, but each individual node will still have its own IP address and network interface. +The VM operations listed above are actually provided by the \link{az_vm_resource} class, and propagated to the template as active bindings. + +A single virtual machine in Azure is actually a collection of resources, including any and all of the following. \itemize{ -\item Storage account -\item Network interface -\item Network security group -\item Virtual network -\item IP address -\item The VM itself +\item Network interface (Azure resource type \code{Microsoft.Network/networkInterfaces}) +\item Network security group (Azure resource type \code{Microsoft.Network/networkSecurityGroups}) +\item Virtual network (Azure resource type \code{Microsoft.Network/virtualNetworks}) +\item Public IP address (Azure resource type \code{Microsoft.Network/publicIPAddresses}) +\item The VM itself (Azure resource type \code{Microsoft.Compute/virtualMachines}) } By wrapping the deployment template used to create these resources, the \code{az_vm_template} class allows managing them all as a single entity. } \section{Methods}{ -The following methods are available, in addition to those provided by the \link[AzureRMR:az_template]{AzureRMR::az_template} class: +The following methods are available, in addition to those provided by the \link[AzureRMR:az_template]{AzureRMR::az_template} class. \itemize{ -\item \code{new(...)}: Initialize a new VM object. See 'Initialization' for more details. \item \code{start(wait=TRUE)}: Start the VM. By default, wait until the startup process is complete. \item \code{stop(deallocate=TRUE, wait=FALSE)}: Stop the VM. By default, deallocate it as well. \item \code{restart(wait=TRUE)}: Restart the VM. \item \code{run_deployed_command(command, parameters, script)}: Run a PowerShell command on the VM. \item \code{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. -\item \code{sync_vm_status()}: Update the VM status fields in this object with information from the host. -\item \code{resize(size, deallocate=FALSE, wait=FALSE)}: Resize the VM. Optionally deallocate it first (may sometimes be necessary). +\item \code{sync_vm_status()}: Check the status of the VM. +\item \code{resize(size, deallocate=FALSE, wait=FALSE)}: Resize the VM. Optionally stop and deallocate it first (may sometimes be necessary). +\item \code{get_public_ip_address(nic=1, config=1)}: Get the public IP address of the VM. Returns NULL if the VM is stopped, or is not publicly accessible. +\item \code{get_private_ip_address(nic=1, config=1)}: Get the private IP address of the VM. +\item \code{add_extension(publisher, type, version, settings=list(), protected_settings=list(), key_vault_settings=list())}: Add an extension to the VM. +\item \code{do_vm_operation(...)}: Carries out an arbitrary operation on the VM resource. See the \code{do_operation} method of the \link[AzureRMR:az_resource]{AzureRMR::az_resource} class for more details. } } -\section{Fields}{ - -The following fields are available, in addition to those provided by the \code{AzureRMR::az_template} class. Each is a list with one element per node in the cluster. -\itemize{ -\item \code{disks}: The status of any attached disks. -\item \code{ip_address}: The IP address. NULL if the node is currently deallocated. -\item \code{dns_name}: The fully qualified domain name. -\item \code{status}: The status of the node, giving the provisioning state and power state. -} -} - -\section{Initialization}{ - -Initializing a new object of this class can either retrieve an existing VM template, or deploy a new VM template on the host. Generally, the best way to initialize an object is via the VM-related methods of the \link{az_subscription} and \link{az_resource_group} class, which handle the details automatically. - -A new VM can be created in \emph{exclusive} mode, meaning a new resource group is created solely to hold the VM. This simplifies deleting a VM considerably, as deleting the resource group will also automatically delete all the VM's resources. This can be done asynchronously, meaning that the \code{delete()} method returns immediately while the process continues on the host. Otherwise, deleting a VM will explicitly delete each of its resources, a task that must be done synchronously to allow for dependencies. -} - \examples{ \dontrun{ -# recommended way to retrieve a VM: via a resource group or subscription object -sub <- AzureRMR::az_rm$ - new(tenant="myaadtenant.onmicrosoft.com", app="app_id", password="password")$ +sub <- AzureRMR::get_azure_login()$ get_subscription("subscription_id") vm <- sub$get_vm("myLinuxDSVM") @@ -85,8 +69,8 @@ vm$sync_vm_status() } } \seealso{ -\link[AzureRMR:az_resource]{AzureRMR::az_resource}, \link{create_vm}, \link{create_vm_cluster}, \link{get_vm}, \link{get_vm_cluster}, \link{list_vms}, -\link{delete_vm}, \link{delete_vm_cluster}, +\link[AzureRMR:az_template]{AzureRMR::az_template}, \link{create_vm}, \link{get_vm}, \link{delete_vm} + \href{https://docs.microsoft.com/en-us/rest/api/compute/virtualmachines}{VM API reference} } \keyword{datasets} diff --git a/man/az_vmss_resource.Rd b/man/az_vmss_resource.Rd new file mode 100644 index 0000000..fe4533c --- /dev/null +++ b/man/az_vmss_resource.Rd @@ -0,0 +1,42 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/az_vmss_resource.R +\docType{class} +\name{az_vmss_resource} +\alias{az_vmss_resource} +\title{Virtual machine scaleset resource class} +\format{An R6 object of class \code{az_vmss_resource}, inheriting from \code{AzureRMR::az_resource}.} +\usage{ +az_vmss_resource +} +\description{ +Class representing a virtual machine scaleset resource. In general, the methods in this class should not be called directly, nor should objects be directly instantiated from it. Use the \code{az_vmss_template} class for interacting with scalesets instead. +} +\section{Methods}{ + +The following methods are available, in addition to those provided by the \link[AzureRMR:az_template]{AzureRMR::az_template} class. +\itemize{ +\item \code{sync_vmss_status}: Check the status of the scaleset. +\item \code{list_instances()}: Return a list of \link{az_vm_resource} objects, one for each VM instance in the scaleset. Note that if the scaleset has a load balancer attached, the number of instances will vary depending on the load. +\item \code{get_instance(id)}: Return a specific VM instance in the scaleset. +\item \code{start(id=NULL, wait=FALSE)}: Start the scaleset. In this and the other methods listed here, \code{id} can be an optional character vector of instance IDs; if supplied, only carry out the operation for those instances. +\item \code{restart(id=NULL, wait=FALSE)}: Restart the scaleset. +\item \code{stop(deallocate=TRUE, id=NULL, wait=FALSE)}: Stop the scaleset. +\item \code{get_public_ip_address()}: Get the public IP address of the scaleset (technically, of the load balancer). If the scaleset doesn't have a load balancer attached, returns NA. +\item \code{get_vm_public_ip_addresses(id=NULL, nic=1, config=1)}: Get the public IP addresses for the instances in the scaleset. Returns NA if the instances are not publicly accessible. +\item \code{get_vm_private_ip_addresses(id=NULL, nic=1, config=1)}: Get the private IP addresses for the instances in the scaleset. +\item \code{run_deployed_command(command, parameters=NULL, script=NULL, id=NULL)}: Run a PowerShell command on the instances in the scaleset. +\item \code{run_script(script, parameters=NULL, id=NULL)}: 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. +\item \code{reimage(id=NULL, datadisks=FALSE)}: Reimage the instances in the scaleset. If \code{datadisks} is TRUE, reimage any attached data disks as well. +\item \code{redeploy(id=NULL)}: Redeploy the instances in the scaleset. +\item \code{mapped_vm_operation(..., id=NULL)}: Carry out an arbitrary operation on the instances in the scaleset. See the \code{do_operation} method of the \link[AzureRMR:az_resource]{AzureRMR::az_resource} class for more details. +\item \code{add_extension(publisher, type, version, settings=list(), protected_settings=list(), key_vault_settings=list())}: Add an extension to the scaleset. +\item \code{do_vmss_operation(...)} Carry out an arbitrary operation on the scaleset resource (as opposed to the instances in the scaleset). +} +} + +\seealso{ +\link[AzureRMR:az_resource]{AzureRMR::az_resource}, \link{get_vm_scaleset_resource}, \link{az_vmss_template} + +\href{https://docs.microsoft.com/en-us/rest/api/compute/virtualmachinescalesets}{VM scaleset API reference} +} +\keyword{datasets} diff --git a/man/az_vmss_template.Rd b/man/az_vmss_template.Rd new file mode 100644 index 0000000..a5d689d --- /dev/null +++ b/man/az_vmss_template.Rd @@ -0,0 +1,77 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/az_vmss_template.R +\docType{class} +\name{az_vmss_template} +\alias{az_vmss_template} +\title{Virtual machine scaleset (cluster) template class} +\format{An R6 object of class \code{az_vmss_template}, inheriting from \code{AzureRMR::az_template}.} +\usage{ +az_vmss_template +} +\description{ +Class representing a virtual machine scaleset deployment template. This class keeps track of all resources that are created as part of deploying a scaleset, and exposes methods for managing them. +} +\details{ +A virtual machine scaleset in Azure is actually a collection of resources, including any and all of the following. +\itemize{ +\item Network security group (Azure resource type \code{Microsoft.Network/networkSecurityGroups}) +\item Virtual network (Azure resource type \code{Microsoft.Network/virtualNetworks}) +\item Load balancer (Azure resource type \code{Microsoft.Network/loadBalancers}) +\item Public IP address (Azure resource type \code{Microsoft.Network/publicIPAddresses}) +\item Autoscaler (Azure resource type \code{Microsoft.Insights/autoscaleSettings}) +\item The scaleset itself (Azure resource type \code{Microsoft.Compute/virtualMachineScaleSets}) +} + +By wrapping the deployment template used to create these resources, the \code{az_vmss_template} class allows managing them all as a single entity. +} +\section{Methods}{ + +The following methods are available, in addition to those provided by the \link[AzureRMR:az_template]{AzureRMR::az_template} class. +\itemize{ +\item \code{sync_vmss_status}: Check the status of the scaleset. +\item \code{list_instances()}: Return a list of \link{az_vm_resource} objects, one for each VM instance in the scaleset. Note that if the scaleset has an autoscaler attached, the number of instances will vary depending on the load. +\item \code{get_instance(id)}: Return a specific VM instance in the scaleset. +\item \code{start(id=NULL, wait=FALSE)}: Start the scaleset. In this and the other methods listed here, \code{id} can be an optional character vector of instance IDs; if supplied, only carry out the operation for those instances. +\item \code{restart(id=NULL, wait=FALSE)}: Restart the scaleset. +\item \code{stop(deallocate=TRUE, id=NULL, wait=FALSE)}: Stop the scaleset. +\item \code{get_public_ip_address()}: Get the public IP address of the scaleset (technically, of the load balancer). If the scaleset doesn't have a load balancer attached, returns NULL. +\item \code{get_vm_public_ip_addresses(id=NULL, nic=1, config=1)}: Get the public IP addresses for the instances in the scaleset. Returns NULL if the instances are not publicly accessible. +\item \code{get_vm_private_ip_addresses(id=NULL, nic=1, config=1)}: Get the private IP addresses for the instances in the scaleset. +\item \code{run_deployed_command(command, parameters=NULL, script=NULL, id=NULL)}: Run a PowerShell command on the instances in the scaleset. +\item \code{run_script(script, parameters=NULL, id=NULL)}: 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. +\item \code{reimage(id=NULL, datadisks=FALSE)}: Reimage the instances in the scaleset. If \code{datadisks} is TRUE, reimage any attached data disks as well. +\item \code{redeploy(id=NULL)}: Redeploy the instances in the scaleset. +\item \code{mapped_vm_operation(..., id=NULL)}: Carry out an arbitrary operation on the instances in the scaleset. See the \code{do_operation} method of the \link[AzureRMR:az_resource]{AzureRMR::az_resource} class for more details. +\item \code{add_extension(publisher, type, version, settings=list(), protected_settings=list(), key_vault_settings=list())}: Add an extension to the scaleset. +\item \code{do_vmss_operation(...)} Carry out an arbitrary operation on the scaleset resource (as opposed to the instances in the scaleset). +} +} + +\examples{ +\dontrun{ + +sub <- AzureRMR::get_azure_login()$ + get_subscription("subscription_id") + +vmss <- sub$get_vm_scaleset("myscaleset") + +# start the VM +vmss$start() + +# run a shell command +vmss$run_script("ifconfig > /tmp/ifc.out") + +# get private IP addresses +vmss$get_vm_private_ip_addresses() + +# get the VM status +vmss$sync_vmss_status() + +} +} +\seealso{ +\link[AzureRMR:az_template]{AzureRMR::az_template}, \link{create_vm_scaleset}, \link{get_vm_scaleset}, \link{delete_vm_scaleset} + +\href{https://docs.microsoft.com/en-us/rest/api/compute/virtualmachinescalesets}{VM scaleset API reference} +} +\keyword{datasets} diff --git a/man/build_template.Rd b/man/build_template.Rd new file mode 100644 index 0000000..f04f639 --- /dev/null +++ b/man/build_template.Rd @@ -0,0 +1,49 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/build_json.R +\name{build_template_definition.vm_config} +\alias{build_template_definition.vm_config} +\alias{build_template_definition.vmss_config} +\alias{build_template_parameters.vm_config} +\alias{build_template_parameters.vmss_config} +\title{Build template definition and parameters} +\usage{ +\method{build_template_definition}{vm_config}(config, ...) + +\method{build_template_definition}{vmss_config}(config, ...) + +\method{build_template_parameters}{vm_config}(config, name, login_user, + size, ...) + +\method{build_template_parameters}{vmss_config}(config, name, login_user, + size, instances, ...) +} +\arguments{ +\item{config}{An object of class \code{vm_config} or \code{vmss_config} representing a virtual machine or scaleset deployment.} + +\item{...}{Unused.} + +\item{name}{The VM or scaleset name. Will also be used for the domain name label, if a public IP address is included in the deployment.} + +\item{login_user}{An object of class \code{user_config} representing the login details for the admin user account on the VM.} + +\item{size}{The VM (instance) size.} + +\item{instances}{For \code{vmss_config}, the number of (initial) instances in the VM scaleset.} +} +\description{ +Build template definition and parameters +} +\details{ +These are methods for the generics defined in the AzureRMR package. +} +\examples{ + +vm <- ubuntu_18.04() +build_template_definition(vm) +build_template_parameters(vm, "myubuntuvm", + user_config("username", "~/.ssh/id_rsa.pub"), "Standard_DS3_v2") + +} +\seealso{ +\link{create_vm}, \link{vm_config}, \link{vmss_config} +} diff --git a/man/create_vm.Rd b/man/create_vm.Rd index bdbd5c3..f5e0563 100644 --- a/man/create_vm.Rd +++ b/man/create_vm.Rd @@ -2,115 +2,167 @@ % Please edit documentation in R/add_methods.R \name{create_vm} \alias{create_vm} -\alias{create_vm_cluster} -\title{Create a new virtual machine or cluster of virtual machines} +\alias{create_vm_scaleset} +\title{Create a new virtual machine or scaleset of virtual machines} \description{ Method for the \link[AzureRMR:az_subscription]{AzureRMR::az_subscription} and \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} classes. } \section{Usage}{ \preformatted{## R6 method for class 'az_resource_group' -create_vm(name, os = c("Windows", "Ubuntu"), size = "Standard_DS3_v2", - username, passkey, userauth_type = c("password", "key"), - ext_file_uris = NULL, inst_command = NULL, - template, parameters, ..., wait = TRUE) +create_vm(name, login_user, size = "Standard_DS3_v2", config = "ubuntu_dsvm", + managed = TRUE, datadisks = numeric(0), ..., + template, parameters, mode = "Incremental", wait = TRUE) ## R6 method for class 'az_subscription' -create_vm(name, location, os = c("Windows", "Ubuntu"), size = "Standard_DS3_v2", - username, passkey, userauth_type = c("password", "key"), - ext_file_uris = NULL, inst_command = NULL, - template, parameters, ..., wait = TRUE) +create_vm(name, ..., resource_group = name, location) ## R6 method for class 'az_resource_group' -create_vm_cluster(name, os = c("Windows", "Ubuntu"), size = "Standard_DS3_v2", - username, passkey, userauth_type = c("password", "key"), - ext_file_uris = NULL, inst_command = NULL, clust_size, - template, parameters, ..., wait = TRUE) +create_vm_scaleset(name, login_user, instances, size = "Standard_DS1_v2", + config = "ubuntu_dsvm_ss", ..., + template, parameters, mode = "Incremental", wait = TRUE) ## R6 method for class 'az_subscription' -create_vm_cluster(name, location, os = c("Windows", "Ubuntu"), size = "Standard_DS3_v2", - username, passkey, userauth_type = c("password", "key"), - ext_file_uris = NULL, inst_command = NULL, clust_size, - template, parameters, ..., wait = TRUE) +create_vm_scaleset(name, ..., resource_group = name, location) } } \section{Arguments}{ \itemize{ -\item \code{name}: The name of the VM or cluster. -\item \code{location}: For the subscription class methods, the location for the VM. Use the \code{list_locations()} method of the \code{AzureRMR::az_subscription} class to see what locations are available. -\item \code{os}: The operating system for the VM. -\item \code{size}: The VM size. Use the \code{list_vm_sizes()} method of the \code{AzureRMR::az_subscription} class to see what sizes are available. -\item \code{username}: The login username for the VM. -\item \code{passkey}: The login password or public key. -\item \code{userauth_type}: The type of login authentication to use. Only has an effect for Linux-based VMs; Windows VMs will always use \code{"password"}. -\item \code{ext_file_uris}: Optional link to download extension packages. -\item \code{inst_command}: If \code{ext_file_uris} is supplied, the install script to run. Defaults to \code{install.sh} for an Ubuntu VM, or \code{install.ps1} for a Windows VM. -\item \code{clust_size}: For a cluster, the number of nodes to create. -\item \code{template}: Optional: the VM template to deploy. By default, this is determined by the values of the other arguments; see 'Details' below. -\item \code{parameters}: Optional: other parameters to pass to the deployment. +\item \code{name}: The name of the VM or scaleset. +\item \code{location}: For the subscription methods, the location for the VM or scaleset. Use the \code{list_locations()} method of the \code{AzureRMR::az_subscription} class to see what locations are available. +\item \code{resource_group}: For the subscription methods, the resource group in which to place the VM or scaleset. Defaults to a new resource group with the same name as the VM. +\item \code{login_user}: The details for the admin login account. An object of class \code{user_config}, obtained by a call to the \code{user_config} function. +\item \code{size}: The VM (instance) size. Use the \link{list_vm_sizes} method to see what sizes are available. +\item \code{config}: The VM or scaleset configuration. See 'Details' below for how to specify this. The default is to use an Ubuntu Data Science Virtual Machine. +\item \code{managed}: For \code{create_vm}, whether the VM should have a managed identity attached. +\item \code{datadisks}: For \code{create_vm}, any data disks to attach to the VM. See 'Details' below. +\item \code{instances}: For \code{create_vm_scaleset}, the initial number of instances in the scaleset. +\item \code{...} For the subscription methods, any of the other arguments listed here, which will be passed to the resource group method. For the resource group method, additional arguments to pass to the VM/scaleset configuration functions \link{vm_config} and \link{vmss_config}. See the examples below. +\item \code{template,parameters}: The template definition and parameters to deploy. By default, these are constructed from the values of the other arguments, but you can supply your own template and/or parameters as well. \item \code{wait}: Whether to wait until the deployment is complete. -\item \code{...}: Other arguments to lower-level methods. +\item \code{mode}: The template deployment mode. If "Complete", any existing resources in the resource group will be deleted. } } \section{Details}{ -This method deploys a template to create a new virtual machine or cluster of VMs. Currently, seven templates are supplied with this package, based on the Azure Data Science Virtual Machine: +These methods deploy a template to create a new virtual machine or scaleset. + +The \code{config} argument can be specified in the following ways: \itemize{ -\item Ubuntu DSVM -\item Ubuntu DSVM using public key authentication -\item Ubuntu DSVM with extensions -\item Ubuntu DSVM cluster -\item Ubuntu DSVM cluster with extensions -\item Windows Server 2016 DSVM -\item Windows Server 2016 DSVM cluster with extensions +\item As the name of a supplied VM or scaleset configuration, like "ubuntu_dsvm" or "ubuntu_dsvm_ss". AzureVM comes with a number of supplied configurations to deploy commonly used images, which can be seen at \link{vm_config} and \link{vmss_config}. Any arguments in \code{...} will be passed to the configuration, allowing you to customise the deployment. +\item As a call to the \code{vm_config} or \code{vmss_config} functions, to deploy a custom VM image. +\item As an object of class \code{vm_config} or \code{vmss_config}. } -An individual virtual machine is treated as a cluster containing only a single node. +The data disks for the VM can be specified as either a vector of numeric disk sizes in GB, or as a list of \code{datadisk_config} objects, created via calls to the \code{datadisk_config} function. Currently, AzureVM only supports creating data disks at deployment time for single VMs, not scalesets. -You can also supply your own VM template for deployment, via the \code{template} argument. See \link[AzureRMR:az_template]{AzureRMR::az_template} for information how to supply templates. Note that if you do this, you may also have to supply a \code{parameters} argument, as the standard parameters for this method are customised for the DSVM. +You can also supply your own template definition and parameters for deployment, via the \code{template} and \code{parameters} arguments. See \link[AzureRMR:az_template]{AzureRMR::az_template} for information how to create templates. -For the \code{AzureRMR::az_subscription} method, this will by default create the VM in \emph{exclusive} mode, meaning a new resource group is created solely to hold the VM. This simplifies managing the VM considerably, in particular deleting the resource group will also automatically delete all the VM's resources. +The \code{AzureRMR::az_subscription} methods will by default create the VM in \emph{exclusive} mode, meaning a new resource group is created solely to hold the VM or scaleset. This simplifies managing the VM considerably; in particular deleting the resource group will also automatically delete all the deployed resources. } \section{Value}{ -An object of class \code{az_vm_template} representing the created VM. +For \code{create_vm}, an object of class \code{az_vm_template} representing the created VM. For \code{create_vm_scaleset}, an object of class \code{az_vmss_template} representing the scaleset. } \examples{ \dontrun{ -sub <- AzureRMR::az_rm$ - new(tenant="myaadtenant.onmicrosoft.com", app="app_id", password="password")$ +sub <- AzureRMR::get_azure_login()$ get_subscription("subscription_id") -# default Windows Server DSVM: make sure to use a strong password! -sub$create_vm("myWindowsDSVM", - location="australiaeast", - username="ds", - passkey="Password123!") +# default Ubuntu 18.04 VM: +# SSH key login, Standard_DS3_v2, publicly accessible via SSH +sub$create_vm("myubuntuvm", user_config("myname", "~/.ssh/id_rsa.pub"), + location="australiaeast") -# upsized Linux (Ubuntu) DSVM -sub$create_vm("myLinuxDSVM", - location="australiaeast", - os="Linux", - username="ds", - passkey=readLines("~/id_rsa.pub"), - size="Standard_DS13_v2") +# Windows Server 2019, with a 500GB datadisk attached, not publicly accessible +sub$create_vm("mywinvm", user_config("myname", password="Use-strong-passwords!"), + size="Standard_DS4_v2", config="windows_2019", datadisks=500, ip=NULL, + location="australiaeast") -sub$create_vm_cluster("myLinuxCluster", - location="australiaeast", - os="Linux", - username="ds", - passkey=readLines("~/id_rsa.pub"), - clust_size=5) +# Ubuntu DSVM, GPU-enabled +sub$create_vm("mydsvm", user_config("myname", "~/.ssh/id_rsa.pub"), size="Standard_NC12", + config="ubuntu_dsvm_ss", + location="australiaeast") + +## custom VM configuration: Windows 10 Pro 1903 with data disks +## this assumes you have a valid Win10 desktop license +user <- user_config("myname", password="Use-strong-passwords!") +image <- image_config( + publisher="MicrosoftWindowsDesktop", + offer="Windows-10", + sku="19h1-pro" +) +datadisks <- list( + datadisk_config(250, type="Premium_LRS"), + datadisk_config(1000, type="Standard_LRS") +) +nsg <- nsg_config( + list(nsg_rule_allow_rdp) +) +config <- vm_config( + image=image, + keylogin=FALSE, + datadisks=datadisks, + nsg=nsg, + properties=list(licenseType="Windows_Client") +) +sub$create_vm("mywin10vm", user, size="Standard_DS2_v2", config=config, + location="australiaeast") + + +# default Ubuntu scaleset: +# load balancer and autoscaler enabled, Standard_DS1_v2 +sub$create_vm_scaleset("mydsvmss", user_config("myname", "~/.ssh/id_rsa.pub"), + instances=5, + location="australiaeast")) + +# Ubuntu DSVM scaleset with public GPU-enabled instances, no load balancer or autoscaler +sub$create_vm_scaleset("mydsvmss", user_config("myname", "~/.ssh/id_rsa.pub"), + instances=5, size="Standard_NC12", config="ubuntu_dsvm_ss", + options=scaleset_options(public=TRUE), + load_balancer=NULL, autoscaler=NULL, + location="australiaeast") + +# RHEL scaleset, allow http/https access +sub$create_vm_scaleset("myrhelss", user_config("myname", "~/.ssh/id_rsa.pub"), + instances=5, config="rhel_8_ss", + nsg=nsg_config(list(nsg_rule_allow_http, nsg_rule_allow_https)), + location="australiaeast") + +# Large Debian scaleset, using low-priority VMs +# need to set the instance size to something that supports low-pri +sub$create_vm_scaleset("mydebss", user_config("myname", "~/.ssh/id_rsa.pub"), + instances=50, size="Standard_DS3_v2", config="debian_9_backports_ss", + options=scaleset_options(low_priority=TRUE, large_scaleset=TRUE), + location="australiaeast") + + +## VM and scaleset in the same resource group and virtual network +# first, create the resgroup +rg <- sub$create_resource_group("rgname", "australiaeast") + +# create the master +rg$create_vm("mastervm", user_config("myname", "~/.ssh/id_rsa.pub")) + +# get the vnet resource +vnet <- rg$get_resource(type="Microsoft.Network/virtualNetworks", name="mastervm-vnet") + +# create the scaleset +rg$create_vm_scaleset("slavess", user_config("myname", "~/.ssh/id_rsa.pub"), + instances=5, vnet=vnet, nsg=NULL, load_balancer=NULL, autoscaler=NULL) } } \seealso{ -\link{az_vm_template}, +\link{az_vm_template}, \link{az_vmss_template} + +\link{vm_config}, \link{vmss_config}, \link{user_config}, \link{datadisk_config} + \link[AzureRMR:az_subscription]{AzureRMR::az_subscription}, \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group}, \href{https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/}{Data Science Virtual Machine} } diff --git a/man/defunct.Rd b/man/defunct.Rd new file mode 100644 index 0000000..03c28e0 --- /dev/null +++ b/man/defunct.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/add_methods.R +\name{defunct} +\alias{defunct} +\alias{get_vm_cluster} +\alias{create_vm_cluster} +\alias{delete_vm_cluster} +\title{Defunct methods} +\description{ +Defunct methods +} +\section{Usage}{ +\preformatted{get_vm_cluster(...) +create_vm_cluster(...) +delete_vm_cluster(...) +} + +These methods for the \code{az_subscription} and \code{az_resource_group} classes are defunct in AzureVM 2.0. To work with virtual machine clusters, call the \link{get_vm_scaleset}, \link{create_vm_scaleset} and \link{delete_vm_scaleset} methods instead. +} + diff --git a/man/delete_vm.Rd b/man/delete_vm.Rd index 9da50e1..4ad763e 100644 --- a/man/delete_vm.Rd +++ b/man/delete_vm.Rd @@ -3,7 +3,7 @@ \docType{class} \name{delete_vm} \alias{delete_vm} -\alias{delete_vm_cluster} +\alias{delete_vm_scaleset} \title{Delete virtual machine} \description{ Method for the \link[AzureRMR:az_subscription]{AzureRMR::az_subscription} and \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} classes. @@ -17,10 +17,10 @@ delete_vm(name, confirm = TRUE, free_resources = TRUE, resource_group = name) ## R6 method for class 'az_resource_group' -delete_vm_cluster(name, confirm = TRUE, free_resources = TRUE) +delete_vm_scaleset(name, confirm = TRUE, free_resources = TRUE) ## R6 method for class 'az_subscription' -delete_vm_cluster(name, confirm = TRUE, free_resources = TRUE, +delete_vm_scaleset(name, confirm = TRUE, free_resources = TRUE, resource_group = name) } } @@ -28,29 +28,21 @@ delete_vm_cluster(name, confirm = TRUE, free_resources = TRUE, \section{Arguments}{ \itemize{ -\item \code{name}: The name of the VM or cluster. +\item \code{name}: The name of the VM or scaleset. \item \code{confirm}: Whether to confirm the delete. \item \code{free_resources}: If this was a deployed template, whether to free all resources created during the deployment process. -\item \code{resource_group}: For the \code{AzureRMR::az_subscription} method, the resource group containing the VM or cluster. +\item \code{resource_group}: For the \code{AzureRMR::az_subscription} method, the resource group containing the VM or scaleset. } } -\section{Details}{ - -If the VM or cluster is of class \link{az_vm_template} and was created in exclusive mode, this method deletes the entire resource group that it occupies. This automatically frees all resources that were created during the deployment process. Otherwise, if \code{free_resources=TRUE}, it manually deletes each individual resource in turn. This is done synchronously (the method does not return until the deletion is complete) to allow for dependencies. - -If the VM is of class \link{az_vm_resource}, this method only deletes the VM resource itself, not any other resources it may depend on. -} - \examples{ \dontrun{ -sub <- AzureRMR::az_rm$ - new(tenant="myaadtenant.onmicrosoft.com", app="app_id", password="password")$ +sub <- AzureRMR::get_azure_login()$ get_subscription("subscription_id") -sub$delete_vm("myWindowsDSVM") -sub$delete_vm("myLinuxDSVM") +sub$delete_vm("myvm") +sub$delete_vm_scaleset("myscaleset") } } diff --git a/man/get_vm.Rd b/man/get_vm.Rd index 561655e..4b1f727 100644 --- a/man/get_vm.Rd +++ b/man/get_vm.Rd @@ -2,8 +2,9 @@ % Please edit documentation in R/add_methods.R \name{get_vm} \alias{get_vm} -\alias{get_vm_cluster} -\alias{list_vms} +\alias{get_vm_scaleset} +\alias{get_vm_resource} +\alias{get_vm_scaleset_resource} \title{Get existing virtual machine(s)} \description{ Method for the \link[AzureRMR:az_subscription]{AzureRMR::az_subscription} and \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} classes. @@ -16,55 +17,51 @@ get_vm(name, resource_group = name) get_vm(name) ## R6 method for class 'az_subscription' -get_vm_cluster(name, resource_group = name) +get_vm_scaleset(name, resource_group = name) ## R6 method for class 'az_resource_group' -get_vm_cluster(name) +get_vm_scaleset(name) -## R6 method for class 'az_resource_group' -## R6 method for class 'az_subscription' -list_vms() +## R6 method for class 'az_resource_group') +get_vm_resource(name) +get_vm_scaleset_resource(name) } } \section{Arguments}{ \itemize{ -\item \code{name}: The name of the VM or cluster. -\item \code{resource_group}: For the \code{az_subscription} method, the resource group in which \code{get_vm()} will look for the VM. Defaults to the VM name. +\item \code{name}: The name of the VM or scaleset. +\item \code{resource_group}: For the \code{az_subscription} methods, the resource group in which \code{get_vm()} and \code{get_vm_scaleset()} will look for the VM or scaleset. Defaults to the VM name. } } -\section{Details}{ - -Despite the names, \code{get_vm} and \code{get_vm_cluster} can both be used to retrieve individual VMs and clusters. The main difference is in their behaviour if a deployment template is not found. In the case of \code{get_vm}, it also searches for a raw VM resource of the given name, whereas \code{get_vm_cluster} will throw an error immediately. -} - \section{Value}{ -For \code{get_vm()}, an object representing the VM, either of class \code{az_vm_template} or \code{az_vm_resource}. +For \code{get_vm()}, an object representing the VM deployment. This will include other resources besides the VM itself, such as the network interface, virtual network, etc. -For \code{list_vms()}, a list of such objects. +For \code{get_vm_scaleset()}, an object representing the scaleset deployment. Similarly to \code{get_vm()}, this includes other resources besides the scaleset. -For \code{get_vm_cluster()}, an object representing the cluster. +For \code{get_vm_resource()} and \code{get_vm_scaleset_resource()}, the VM or scaleset resource itself. } \examples{ \dontrun{ -sub <- AzureRMR::az_rm$ - new(tenant="myaadtenant.onmicrosoft.com", app="app_id", password="password")$ +sub <- AzureRMR::get_azure_login()$ get_subscription("subscription_id") -sub$list_vms() -sub$get_vm("myVirtualMachine") +sub$get_vm("myvirtualmachine") +sub$get_vm_scaleset("myscaleset") rg <- sub$get_resource_group("rgname") -rg$get_vm("myOtherVirtualMachine") +rg$get_vm("myothervirtualmachine") +rg$get_vm_scaleset("myotherscaleset") } } \seealso{ -\link{az_vm_template}, \link{az_vm_resource}, +\link{az_vm_template}, \link{az_vm_resource}, \link{az_vmss_template}, \link{az_vmss_resource} for the methods available for working with VMs and VM scalesets. + \link[AzureRMR:az_subscription]{AzureRMR::az_subscription}, \link[AzureRMR:az_resource_group]{AzureRMR::az_resource_group} } diff --git a/man/ip_config.Rd b/man/ip_config.Rd new file mode 100644 index 0000000..94b521f --- /dev/null +++ b/man/ip_config.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ip_config.R +\name{ip_config} +\alias{ip_config} +\title{Public IP address configuration} +\usage{ +ip_config(type = NULL, dynamic = NULL, ipv6 = FALSE, + domain_name = "[parameters('vmName')]", ...) +} +\arguments{ +\item{type}{The SKU of the IP address resource: "basic" or "standard". If NULL (the default), this will be determined based on the VM's configuration.} + +\item{dynamic}{Whether the IP address should be dynamically or statically allocated. Note that the standard SKU only supports standard allocation. If NULL (the default) this will be determined based on the VM's configuration.} + +\item{ipv6}{Whether to create an IPv6 address. The default is IPv4.} + +\item{domain_name}{The domain name label to associate with the address.} + +\item{...}{Other named arguments that will be treated as resource properties.} +} +\description{ +Public IP address configuration +} +\seealso{ +\link{create_vm}, \link{vm_config}, \link{vmss_config} +} diff --git a/man/is_vm.Rd b/man/is_vm.Rd new file mode 100644 index 0000000..b1687bd --- /dev/null +++ b/man/is_vm.Rd @@ -0,0 +1,41 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/is_vm.R +\name{is_vm} +\alias{is_vm} +\alias{is_vm_template} +\alias{is_vm_resource} +\alias{is_vm_scaleset} +\alias{is_vm_scaleset_template} +\alias{is_vm_scaleset_resource} +\title{Is an object an Azure VM} +\usage{ +is_vm(object) + +is_vm_template(object) + +is_vm_resource(object) + +is_vm_scaleset(object) + +is_vm_scaleset_template(object) + +is_vm_scaleset_resource(object) +} +\arguments{ +\item{object}{an R object.} +} +\value{ +\code{is_vm} and \code{is_vm_template} return TRUE for an object representing a virtual machine deployment (which will include other resources besides the VM itself). + +\code{is_vm_resource} returns TRUE for an object representing the specific VM resource. + +\code{is_vm_scaleset} and \code{is_vm_scaleset_template} return TRUE for an object representing a VM scaleset deployment. + +\code{is_vm_scaleset_resource} returns TRUE for an object representing the specific VM scaleset resource. +} +\description{ +Is an object an Azure VM +} +\seealso{ +\link{create_vm}, \link{create_vm_scaleset}, \link{az_vm_template}, \link{az_vm_resource}, \link{az_vmss_template}, \link{az_vmss_resource} +} diff --git a/man/is_vm_template.Rd b/man/is_vm_template.Rd deleted file mode 100644 index adf052f..0000000 --- a/man/is_vm_template.Rd +++ /dev/null @@ -1,20 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/az_vm_template.R -\name{is_vm_template} -\alias{is_vm_template} -\title{Is an object an Azure VM template} -\usage{ -is_vm_template(object) -} -\arguments{ -\item{object}{an R object.} -} -\value{ -A boolean. -} -\description{ -Is an object an Azure VM template -} -\details{ -This function returns TRUE only for an object representing a VM template deployment. In particular, it returns FALSE for a raw VM resource. -} diff --git a/man/lb_config.Rd b/man/lb_config.Rd new file mode 100644 index 0000000..440ff8f --- /dev/null +++ b/man/lb_config.Rd @@ -0,0 +1,48 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/lb_config.R +\name{lb_config} +\alias{lb_config} +\alias{lb_probe} +\alias{lb_rule} +\title{Load balancer configuration} +\usage{ +lb_config(type = NULL, rules = list(), probes = list(), ...) + +lb_probe(name, port, interval = 5, fail_on = 2, protocol = "Tcp") + +lb_rule(name, frontend_port, backend_port = frontend_port, + protocol = "Tcp", timeout = 5, floating_ip = FALSE, probe_name) +} +\arguments{ +\item{type}{The SKU of the load balancer resource: "basic" or "standard". If NULL (the default), this will be determined based on the VM scaleset's configuration. Note that the load balancer SKU must be the same as that of its public IP address.} + +\item{rules}{A list of load balancer rules, each obtained via a call to \code{lb_rule}.} + +\item{probes}{A list of health checking probes, each obtained via a call to \code{lb_probe}. There must be a probe corresponding to each rule.} + +\item{...}{Other named arguments that will be treated as resource properties.} + +\item{name}{For \code{lb_rule}, a name for the load balancing rule.} + +\item{port}{For \code{lb_probe}, the port to probe.} + +\item{interval}{For \code{lb_probe}, the time interval between probes in seconds.} + +\item{fail_on}{For \code{lb_probe}, the probe health check will fail after this many non-responses.} + +\item{protocol}{For \code{lb_probe} and \code{lb_rule}, the protocol: either "Tcp" or "Ip".} + +\item{frontend_port, backend_port}{For \code{lb_rule}, the ports for this rule.} + +\item{timeout}{The timeout interval for the rule. The default is 5 minutes.} + +\item{floating_ip}{Whether to use floating IP addresses (direct server return). Only needed for specific scenarios, and when the frontend and backend ports don't match.} + +\item{probe_name}{The name of the corresponding health check probe.} +} +\description{ +Load balancer configuration +} +\seealso{ +\link{create_vm_scaleset}, \link{vmss_config}, \link{lb_rules} for some predefined load balancing rules and probes +} diff --git a/man/lb_rules.Rd b/man/lb_rules.Rd new file mode 100644 index 0000000..8a98efc --- /dev/null +++ b/man/lb_rules.Rd @@ -0,0 +1,76 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/lb_config.R +\docType{data} +\name{lb_rule_ssh} +\alias{lb_rule_ssh} +\alias{lb_rules} +\alias{lb_rule_http} +\alias{lb_rule_https} +\alias{lb_rule_rdp} +\alias{lb_rule_jupyter} +\alias{lb_rule_rstudio} +\alias{lb_rule_mssql} +\alias{lb_rule_mssql_browser} +\alias{lb_probe_ssh} +\alias{lb_probe_http} +\alias{lb_probe_https} +\alias{lb_probe_rdp} +\alias{lb_probe_jupyter} +\alias{lb_probe_rstudio} +\alias{lb_probe_mssql} +\alias{lb_probe_mssql_browser} +\title{Load balancing rules} +\format{Objects of class \code{lb_rule} and \code{lb_probe}.} +\usage{ +lb_rule_ssh + +lb_rule_http + +lb_rule_https + +lb_rule_rdp + +lb_rule_jupyter + +lb_rule_rstudio + +lb_rule_mssql + +lb_rule_mssql_browser + +lb_probe_ssh + +lb_probe_http + +lb_probe_https + +lb_probe_rdp + +lb_probe_jupyter + +lb_probe_rstudio + +lb_probe_mssql + +lb_probe_mssql_browser +} +\description{ +Load balancing rules +} +\details{ +Some predefined load balancing objects, for commonly used ports. Each load balancing rule comes with its own health probe. +\itemize{ +\item HTTP: TCP port 80 +\item HTTPS: TCP port 443 +\item JupyterHub: TCP port 8000 +\item RDP: TCP port 3389 +\item RStudio Server: TCP port 8787 +\item SSH: TCP port 22 +\item SQL Server: TCP port 1433 +\item SQL Server browser service: TCP port 1434 +} +} +\seealso{ +\link{lb_config} +} +\keyword{datasets} diff --git a/man/list_vm_sizes.Rd b/man/list_vm_sizes.Rd index 5cf97c2..39385e1 100644 --- a/man/list_vm_sizes.Rd +++ b/man/list_vm_sizes.Rd @@ -31,8 +31,7 @@ If \code{name_only} is TRUE, a character vector of names, suitable for passing t \examples{ \dontrun{ -sub <- AzureRMR::az_rm$ - new(tenant="myaadtenant.onmicrosoft.com", app="app_id", password="password")$ +sub <- AzureRMR::get_azure_login$ get_subscription("subscription_id") sub$list_vm_sizes("australiaeast") diff --git a/man/nic_config.Rd b/man/nic_config.Rd new file mode 100644 index 0000000..9681868 --- /dev/null +++ b/man/nic_config.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/nic_config.R +\name{nic_config} +\alias{nic_config} +\alias{nic_ip_config} +\title{Network interface configuration} +\usage{ +nic_config(nic_ip = list(nic_ip_config()), ...) + +nic_ip_config(name = "ipconfig", private_alloc = "dynamic", + subnet = "[variables('subnetId')]", + public_address = "[variables('ipId')]", ...) +} +\arguments{ +\item{nic_ip}{For \code{nic_config}, a list of IP configuration objects, each obtained via a call to \code{nic_ip_config}.} + +\item{...}{Other named arguments that will be treated as resource properties.} + +\item{name}{For \code{nic_ip_config}, the name of the IP configuration.} + +\item{private_alloc}{For \code{nic_ip_config}, the allocation method for a private IP address. Can be "dynamic" or "static".} + +\item{subnet}{For \code{nic_ip_config}, the subnet to associate with this private IP address.} + +\item{public_address}{For \code{nic_ip_config}, the public IP address. Defaults to the public IP address created or used as part of this VM deployment. Ignored if the deployment does not include a public address.} +} +\description{ +Network interface configuration +} +\seealso{ +\link{create_vm}, \link{vm_config} +} diff --git a/man/nsg_config.Rd b/man/nsg_config.Rd new file mode 100644 index 0000000..4cb2441 --- /dev/null +++ b/man/nsg_config.Rd @@ -0,0 +1,39 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/nsg_config.R +\name{nsg_config} +\alias{nsg_config} +\alias{nsg_rule} +\title{Network security group configuration} +\usage{ +nsg_config(rules = list(), ...) + +nsg_rule(name, dest_port = "*", dest_addr = "*", dest_asgs = NULL, + source_port = "*", source_addr = "*", source_asgs = NULL, + access = "allow", direction = "inbound", protocol = "Tcp", + priority = NULL) +} +\arguments{ +\item{rules}{for \code{nsg_config}, a list of security rule objects, each obtained via a call to \code{nsg_rule}.} + +\item{...}{Other named arguments that will be treated as resource properties.} + +\item{name}{For \code{nsg_rule}, a name for the rule.} + +\item{dest_port, dest_addr, dest_asgs}{For \code{nsg_rule}, the destination port, address range, and application security groups for a rule.} + +\item{source_port, source_addr, source_asgs}{For \code{nsg_rule}, the source port, address range, and application security groups for a rule.} + +\item{access}{For \code{nsg_rule}, the action to take: "allow" or "deny".} + +\item{direction}{For \code{nsg_rule}, the direction of traffic: "inbound" or "outbound".} + +\item{protocol}{For \code{nsg_rule}, the network protocol: either "Tcp" or "Udp".} + +\item{priority}{For \code{nsg_rule}, the rule priority. If NULL, this will be set automatically by AzureVM.} +} +\description{ +Network security group configuration +} +\seealso{ +\link{create_vm}, \link{vm_config}, \link{vmss_config}, \link{nsg_rules} for some predefined security rules +} diff --git a/man/nsg_rules.Rd b/man/nsg_rules.Rd new file mode 100644 index 0000000..9c1f1cb --- /dev/null +++ b/man/nsg_rules.Rd @@ -0,0 +1,52 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/nsg_config.R +\docType{data} +\name{nsg_rule_allow_ssh} +\alias{nsg_rule_allow_ssh} +\alias{nsg_rules} +\alias{nsg_rule_allow_http} +\alias{nsg_rule_allow_https} +\alias{nsg_rule_allow_rdp} +\alias{nsg_rule_allow_jupyter} +\alias{nsg_rule_allow_rstudio} +\alias{nsg_rule_allow_mssql} +\alias{nsg_rule_allow_mssql_browser} +\title{Network security rules} +\format{Objects of class \code{nsg_rule}.} +\usage{ +nsg_rule_allow_ssh + +nsg_rule_allow_http + +nsg_rule_allow_https + +nsg_rule_allow_rdp + +nsg_rule_allow_jupyter + +nsg_rule_allow_rstudio + +nsg_rule_allow_mssql + +nsg_rule_allow_mssql_browser +} +\description{ +Network security rules +} +\details{ +Some predefined network security rule objects, to unblock commonly used ports. +\itemize{ +\item HTTP: TCP port 80 +\item HTTPS: TCP port 443 +\item JupyterHub: TCP port 8000 +\item RDP: TCP port 3389 +\item RStudio Server: TCP port 8787 +\item SSH: TCP port 22 +\item SQL Server: TCP port 1433 +\item SQL Server browser service: TCP port 1434 +} +} +\seealso{ +\link{nsg_config} +} +\keyword{datasets} diff --git a/man/reexports.Rd b/man/reexports.Rd new file mode 100644 index 0000000..2ad0af3 --- /dev/null +++ b/man/reexports.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/AzureVM.R +\docType{import} +\name{reexports} +\alias{reexports} +\alias{build_template_definition} +\alias{build_template_parameters} +\title{Objects exported from other packages} +\keyword{internal} +\description{ +These objects are imported from other packages. Follow the links +below to see their documentation. + +\describe{ + \item{AzureRMR}{\code{\link[AzureRMR]{build_template_definition}}, \code{\link[AzureRMR]{build_template_parameters}}} +}} + diff --git a/man/scaleset_options.Rd b/man/scaleset_options.Rd new file mode 100644 index 0000000..c9b92fd --- /dev/null +++ b/man/scaleset_options.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vmss_config.R +\name{scaleset_options} +\alias{scaleset_options} +\title{Virtual machine scaleset options} +\usage{ +scaleset_options(keylogin = TRUE, managed = TRUE, public = FALSE, + low_priority = FALSE, delete_on_evict = FALSE, + network_accel = FALSE, large_scaleset = FALSE, + overprovision = TRUE, upgrade_policy = list(mode = "manual")) +} +\arguments{ +\item{keylogin}{Boolean: whether to use an SSH public key to login (TRUE) or a password (FALSE). Note that Windows does not support SSH key logins.} + +\item{managed}{Whether to provide a managed system identity for the VM.} + +\item{public}{Whether the instances (nodes) of the scaleset should be visible from the public internet.} + +\item{low_priority}{Whether to use low-priority VMs. Note that this option is only available for certain VM sizes.} + +\item{delete_on_evict}{If low-priority VMs are being used, whether evicting (shutting down) a VM should delete it, as opposed to just deallocating it.} + +\item{network_accel}{Whether to enable accelerated networking.} + +\item{large_scaleset}{Whether to enable scaleset sizes > 100 instances.} + +\item{overprovision}{Whether to overprovision the scaleset on creation.} + +\item{upgrade_policy}{A list, giving the VM upgrade policy for the scaleset.} +} +\description{ +Virtual machine scaleset options +} diff --git a/man/vm_config.Rd b/man/vm_config.Rd new file mode 100644 index 0000000..0181f91 --- /dev/null +++ b/man/vm_config.Rd @@ -0,0 +1,182 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vm_config.R +\name{vm_config} +\alias{vm_config} +\alias{ubuntu_dsvm} +\alias{windows_dsvm} +\alias{ubuntu_16.04} +\alias{ubuntu_18.04} +\alias{windows_2016} +\alias{windows_2019} +\alias{rhel_7.6} +\alias{rhel_8} +\alias{debian_9_backports} +\title{VM configuration functions} +\usage{ +vm_config(image, keylogin, managed = TRUE, datadisks = numeric(0), + nsg = nsg_config(), ip = ip_config(), vnet = vnet_config(), + nic = nic_config(), other_resources = list(), variables = list(), + ...) + +ubuntu_dsvm(keylogin = TRUE, managed = TRUE, datadisks = numeric(0), + nsg = nsg_config(list(nsg_rule_allow_ssh, nsg_rule_allow_jupyter, + nsg_rule_allow_rstudio)), ...) + +windows_dsvm(keylogin = FALSE, managed = TRUE, + datadisks = numeric(0), nsg = nsg_config(list(nsg_rule_allow_rdp)), + ...) + +ubuntu_16.04(keylogin = TRUE, managed = TRUE, datadisks = numeric(0), + nsg = nsg_config(list(nsg_rule_allow_ssh)), ...) + +ubuntu_18.04(keylogin = TRUE, managed = TRUE, datadisks = numeric(0), + nsg = nsg_config(list(nsg_rule_allow_ssh)), ...) + +windows_2016(keylogin = FALSE, managed = TRUE, + datadisks = numeric(0), nsg = nsg_config(list(nsg_rule_allow_rdp)), + ...) + +windows_2019(keylogin = FALSE, managed = TRUE, + datadisks = numeric(0), nsg = nsg_config(list(nsg_rule_allow_rdp)), + ...) + +rhel_7.6(keylogin = TRUE, managed = TRUE, datadisks = numeric(0), + nsg = nsg_config(list(nsg_rule_allow_ssh)), ...) + +rhel_8(keylogin = TRUE, managed = TRUE, datadisks = numeric(0), + nsg = nsg_config(list(nsg_rule_allow_ssh)), ...) + +debian_9_backports(keylogin = TRUE, managed = TRUE, + datadisks = numeric(0), nsg = nsg_config(list(nsg_rule_allow_ssh)), + ...) +} +\arguments{ +\item{image}{For \code{vm_config}, the VM image to deploy. This should be an object of class \code{image_config}, created by the function of the same name.} + +\item{keylogin}{Boolean: whether to use an SSH public key to login (TRUE) or a password (FALSE). Note that Windows does not support SSH key logins.} + +\item{managed}{Whether to provide a managed system identity for the VM.} + +\item{datadisks}{The data disks to attach to the VM. Specify this as either a vector of numeric disk sizes in GB, or a list of \code{datadisk_config} objects for more control over the specification.} + +\item{nsg}{The network security group for the VM. Can be a call to \code{nsg_config} to create a new NSG; an AzureRMR resource object or resource ID to reuse an existing NSG; or NULL to not use an NSG (not recommended).} + +\item{ip}{The public IP address for the VM. Can be a call to \code{ip_config} to create a new IP address; an AzureRMR resource object or resource ID to reuse an existing address resource; or NULL if the VM should not be accessible from outside its subnet.} + +\item{vnet}{The virtual network for the VM. Can be a call to \code{vnet_config} to create a new virtual network, or an AzureRMR resource object or resource ID to reuse an existing virtual network. Note that by default, AzureVM will associate the NSG with the virtual network/subnet, not with the VM's network interface.} + +\item{nic}{The network interface for the VM. Can be a call to \code{nic_config} to create a new interface, or an AzureRMR resource object or resource ID to reuse an existing interface.} + +\item{other_resources}{An optional list of other resources to include in the deployment.} + +\item{variables}{An optional named list of variables to add to the template.} + +\item{...}{For the specific VM configurations, other customisation arguments to be passed to \code{vm_config}. For \code{vm_config}, named arguments that will be folded into the VM resource definition in the template.} +} +\value{ +An object of S3 class \code{vm_config}, that can be used by the \code{create_vm} method. +} +\description{ +VM configuration functions +} +\details{ +These functions are for specifying the details of a new virtual machine deployment: the VM image and related options, along with the Azure resources that the VM may need. These include the datadisks, network security group, public IP address (if the VM is to be accessible from outside its subnet), virtual network, and network interface. + +Each resource can be specified in a number of ways: +\itemize{ +\item To \emph{create} a new resource as part of the deployment, call the corresponding \code{*_config} function. +\item To use an \emph{existing} resource, supply either an \code{AzureRMR::az_resource} object (recommended) or a string containing the resource ID. +\item If the resource is not needed, specify it as NULL. +\item For the \code{other_resources} argument, supply a list of resources, each of which should be a list of resource fields (name, type, properties, sku, etc). +} + +The \code{vm_config} function is the base configuration function, and the others call it to create VMs with specific operating systems and other image details. +\itemize{ +\item \code{ubuntu_dsvm}: Data Science Virtual Machine, based on Ubuntu 16.04 +\item \code{windows_dsvm}: Data Science Virtual Machine, based on Windows Server 2016 +\item \code{ubuntu_16.04}, \code{ubuntu_18.04}: Ubuntu +\item \code{windows_2016}, \code{windows_2019}: Windows Server Datacenter edition +\item \code{rhel_7.6}, \code{rhel_8}: Red Hat Enterprise Linux +\item \code{debian_9_backports}: Debian +} +} +\examples{ + +# basic Linux (Ubuntu) and Windows configs +ubuntu_18.04() +windows_2019() + +# Windows DSVM with 500GB data disk, no public IP address +windows_dsvm(datadisks=500, ip=NULL) + +# RHEL VM exposing ports 80 (HTTP) and 443 (HTTPS) +rhel_8(nsg=nsg_config(nsg_rule_allow_http, nsg_rule_allow_https)) + +# exposing no ports externally +rhel_8(nsg=nsg_config(list())) + +# deploying an extra resource: storage account +ubuntu_18.04( + variables=list(storName="[concat(variables('vmName'), 'stor')]"), + other_resources=list( + list( + type="Microsoft.Storage/storageAccounts", + name="[variables('storName')]", + apiVersion="2018-07-01", + location="[variables('location')]", + properties=list(supportsHttpsTrafficOnly=TRUE), + sku=list(name="Standard_LRS"), + kind="Storage" + ) + ) +) + +## custom VM configuration: Windows 10 Pro 1903 with data disks +## this assumes you have a valid Win10 desktop license +user <- user_config("myname", password="Use-strong-passwords!") +image <- image_config( + publisher="MicrosoftWindowsDesktop", + offer="Windows-10", + sku="19h1-pro" +) +datadisks <- list( + datadisk_config(250, type="Premium_LRS"), + datadisk_config(1000, type="Standard_LRS") +) +nsg <- nsg_config( + list(nsg_rule_allow_rdp) +) +vm_config( + image=image, + keylogin=FALSE, + datadisks=datadisks, + nsg=nsg, + properties=list(licenseType="Windows_Client") +) + + +\dontrun{ + +# reusing existing resources: placing multiple VMs in one vnet/subnet +rg <- AzureRMR::get_azure_login()$ + get_subscription("sub_id")$ + get_resource_group("rgname") + +vnet <- rg$get_resource(type="Microsoft.Network/virtualNetworks", name="myvnet") + +# by default, the NSG is associated with the subnet, so we don't need a new NSG either +vmconfig1 <- ubuntu_18.04(vnet=vnet, nsg=NULL) +vmconfig2 <- debian_9_backports(vnet=vnet, nsg=NULL) +vmconfig3 <- windows_2019(vnet=vnet, nsg=NULL) + +} +} +\seealso{ +\link{image_config}, \link{user_config}, \link{datadisk_config} for options relating to the VM resource itself + +\link{nsg_config}, \link{ip_config}, \link{vnet_config}, \link{nic_config} for other resource configs + +\link{vmss_config} for configuring a virtual machine scaleset + +\link{create_vm} +} diff --git a/man/vm_resource_config.Rd b/man/vm_resource_config.Rd new file mode 100644 index 0000000..9969aa2 --- /dev/null +++ b/man/vm_resource_config.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vm_resource_config.R +\name{user_config} +\alias{user_config} +\alias{datadisk_config} +\alias{image_config} +\title{Resource configuration functions for a virtual machine deployment} +\usage{ +user_config(username, sshkey = NULL, password = NULL) + +datadisk_config(size, name = "datadisk", create = "empty", + type = "StandardSSD_LRS", write_accelerator = FALSE) + +image_config(publisher = NULL, offer = NULL, sku = NULL, + version = "latest", id = NULL) +} +\arguments{ +\item{username}{For \code{user_config}, the name for the admin user account.} + +\item{sshkey}{For \code{user_config}, string containing an SSH public key. Can be the key itself, or the name of the public key file.} + +\item{password}{For \code{user_config}, the admin password. Supply either \code{sshkey} or \code{password}, but not both; also, note that Windows does not support SSH logins.} + +\item{size}{For \code{datadisk_config}, the size of the data disk in GB. St this to NULL for a disk that will be created from an image.} + +\item{name}{For \code{datadisk_config}, the disk name. Duplicate names will automatically be disambiguated prior to VM deployment.} + +\item{create}{For \code{datadisk_config}, the creation method. Can be "empty" (the default) to create a blank disk, or "fromImage" to use an image.} + +\item{type}{For \code{datadisk_config}, the disk type (SKU). Can be "Standard_LRS", "StandardSSD_LRS" (the default), "Premium_LRS" or "UltraSSD_LRS". Of these, "Standard_LRS" uses hard disks and the others use SSDs as the underlying hardware.} + +\item{write_accelerator}{For \code{datadisk_config}, whether the disk should have write acceleration enabled.} + +\item{publisher, offer, sku, version}{For \code{image_config}, the details for a marketplace image.} + +\item{id}{For \code{image_config}, the resource ID for a disk to use as a custom image.} +} +\description{ +Resource configuration functions for a virtual machine deployment +} diff --git a/man/vmss_config.Rd b/man/vmss_config.Rd new file mode 100644 index 0000000..02a56b1 --- /dev/null +++ b/man/vmss_config.Rd @@ -0,0 +1,148 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vmss_config.R +\name{vmss_config} +\alias{vmss_config} +\alias{ubuntu_dsvm_ss} +\alias{windows_dsvm_ss} +\alias{ubuntu_16.04_ss} +\alias{ubuntu_18.04_ss} +\alias{windows_2016_ss} +\alias{windows_2019_ss} +\alias{rhel_7.6_ss} +\alias{rhel_8_ss} +\alias{debian_9_backports_ss} +\title{Virtual machine scaleset configuration functions} +\usage{ +vmss_config(image, options = scaleset_options(), nsg = nsg_config(), + vnet = vnet_config(), load_balancer = lb_config(), + load_balancer_address = ip_config(), + autoscaler = autoscaler_config(), other_resources = list(), + variables = list(), ...) + +ubuntu_dsvm_ss(nsg = nsg_config(list(nsg_rule_allow_ssh, + nsg_rule_allow_jupyter, nsg_rule_allow_rstudio)), + load_balancer = lb_config(rules = list(lb_rule_ssh, lb_rule_jupyter, + lb_rule_rstudio), probes = list(lb_probe_ssh, lb_probe_jupyter, + lb_probe_rstudio)), ...) + +windows_dsvm_ss(nsg = nsg_config(list(nsg_rule_allow_rdp)), + load_balancer = lb_config(rules = list(lb_rule_rdp), probes = + list(lb_probe_rdp)), options = scaleset_options(keylogin = FALSE), ...) + +ubuntu_16.04_ss(nsg = nsg_config(list(nsg_rule_allow_ssh)), + load_balancer = lb_config(rules = list(lb_rule_ssh), probes = + list(lb_probe_ssh)), ...) + +ubuntu_18.04_ss(nsg = nsg_config(list(nsg_rule_allow_ssh)), + load_balancer = lb_config(rules = list(lb_rule_ssh), probes = + list(lb_probe_ssh)), ...) + +windows_2016_ss(nsg = nsg_config(list(nsg_rule_allow_rdp)), + load_balancer = lb_config(rules = list(lb_rule_rdp), probes = + list(lb_probe_rdp)), options = scaleset_options(keylogin = FALSE), ...) + +windows_2019_ss(nsg = nsg_config(list(nsg_rule_allow_rdp)), + load_balancer = lb_config(rules = list(lb_rule_rdp), probes = + list(lb_probe_rdp)), options = scaleset_options(keylogin = FALSE), ...) + +rhel_7.6_ss(nsg = nsg_config(list(nsg_rule_allow_ssh)), + load_balancer = lb_config(rules = list(lb_rule_ssh), probes = + list(lb_probe_ssh)), ...) + +rhel_8_ss(nsg = nsg_config(list(nsg_rule_allow_ssh)), + load_balancer = lb_config(rules = list(lb_rule_ssh), probes = + list(lb_probe_ssh)), ...) + +debian_9_backports_ss(nsg = nsg_config(list(nsg_rule_allow_ssh)), + load_balancer = lb_config(rules = list(lb_rule_ssh), probes = + list(lb_probe_ssh)), ...) +} +\arguments{ +\item{image}{For \code{vmss_config}, the VM image to deploy. This should be an object of class \code{image_config}, created by the function of the same name.} + +\item{options}{Scaleset options, as obtained via a call to \code{scaleset_options}.} + +\item{nsg}{The network security group for the scaleset. Can be a call to \code{nsg_config} to create a new NSG; an AzureRMR resource object or resource ID to reuse an existing NSG; or NULL to not use an NSG (not recommended).} + +\item{vnet}{The virtual network for the scaleset. Can be a call to \code{vnet_config} to create a new virtual network, or an AzureRMR resource object or resource ID to reuse an existing virtual network. Note that by default, AzureVM will associate the NSG with the virtual network/subnet, not with the VM's network interface.} + +\item{load_balancer}{The load balancer for the scaleset. Can be a call to \code{lb_config} to create a new load balancer; an AzureRMR resource object or resource ID to reuse an existing load balancer; or NULL if load balancing is not required.} + +\item{load_balancer_address}{The public IP address for the load balancer. Can be a call to \code{ip_config} to create a new IP address, or an AzureRMR resource object or resource ID to reuse an existing address resource. Ignored if \code{load_balancer} is NULL.} + +\item{autoscaler}{The autoscaler for the scaleset. Can be a call to \code{autoscaler_config} to create a new autoscaler; an AzureRMR resource object or resource ID to reuse an existing autoscaler; or NULL if autoscaling is not required.} + +\item{other_resources}{An optional list of other resources to include in the deployment.} + +\item{variables}{An optional named list of variables to add to the template.} + +\item{...}{For the specific VM configurations, other customisation arguments to be passed to \code{vm_config}. For \code{vmss_config}, named arguments that will be folded into the scaleset resource definition in the template.} +} +\value{ +An object of S3 class \code{vmss_config}, that can be used by the \code{create_vm_scaleset} method. +} +\description{ +Virtual machine scaleset configuration functions +} +\details{ +These functions are for specifying the details of a new virtual machine scaleset deployment: the base VM image and related options, along with the Azure resources that the scaleset may need. These include the network security group, virtual network, load balancer and associated public IP address, and autoscaler. + +Each resource can be specified in a number of ways: +\itemize{ +\item To \emph{create} a new resource as part of the deployment, call the corresponding \code{*_config} function. +\item To use an \emph{existing} resource, supply either an \code{AzureRMR::az_resource} object (recommended) or a string containing the resource ID. +\item If the resource is not needed, specify it as NULL. +\item For the \code{other_resources} argument, supply a list of resources, each of which should be a list of resource fields (name, type, properties, sku, etc). +} + +The \code{vmss_config} function is the base configuration function, and the others call it to create VM scalesets with specific operating systems and other image details. +\itemize{ +\item \code{ubuntu_dsvm_ss}: Data Science Virtual Machine, based on Ubuntu 16.04 +\item \code{windows_dsvm_ss}: Data Science Virtual Machine, based on Windows Server 2016 +\item \code{ubuntu_16.04_ss}, \code{ubuntu_18.04}: Ubuntu +\item \code{windows_2016_ss}, \code{windows_2019}: Windows Server Datacenter edition +\item \code{rhel_7.6_ss}, \code{rhel_8_ss}: Red Hat Enterprise Linux +\item \code{debian_9_backports_ss}: Debian +} +} +\examples{ + +# basic Linux (Ubuntu) and Windows configs +ubuntu_18.04_ss() +windows_2019_ss() + +# Windows DSVM scaleset, no load balancer and autoscaler +windows_dsvm_ss(load_balancer=NULL, autoscaler=NULL) + +# RHEL VM exposing ports 80 (HTTP) and 443 (HTTPS) +rhel_8_ss(nsg=nsg_config(nsg_rule_allow_http, nsg_rule_allow_https)) + +# exposing no ports externally +rhel_8_ss(nsg=nsg_config(list())) + +# low-priority VMs, large scaleset (>100 instances allowed), no managed identity +rhel_8_ss(options=scaleset_options(low_priority=TRUE, large_scaleset=TRUE, managed=FALSE)) + + +\dontrun{ + +# reusing existing resources: placing a scaleset in an existing vnet/subnet +# we don't need a new network security group either +vnet <- AzureRMR::get_azure_login()$ + get_subscription("sub_id")$ + get_resource_group("rgname")$ + get_resource(type="Microsoft.Network/virtualNetworks", name="myvnet") + +ubuntu_18.04_ss(vnet=vnet, nsg=NULL) + +} +} +\seealso{ +\link{scaleset_options} for options relating to the scaleset resource itself + +\link{nsg_config}, \link{ip_config}, \link{vnet_config}, \link{lb_config}, \link{autoscaler_config} for other resource configs + +\link{vm_config} for configuring an individual virtual machine + +\link{create_vm_scaleset} +} diff --git a/man/vnet_config.Rd b/man/vnet_config.Rd new file mode 100644 index 0000000..d48f537 --- /dev/null +++ b/man/vnet_config.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vnet_config.R +\name{vnet_config} +\alias{vnet_config} +\alias{subnet_config} +\title{Virtual network configuration} +\usage{ +vnet_config(address_space = "10.0.0.0/16", + subnets = list(subnet_config()), ...) + +subnet_config(name = "subnet", addresses = "10.0.0.0/16", + nsg = "[variables('nsgId')]", ...) +} +\arguments{ +\item{address_space}{For \code{vnet_config}, the address range accessible by the virtual network, expressed in CIDR block format.} + +\item{subnets}{For \code{vnet_config}, a list of subnet objects, each obtained via a call to \code{subnet_config}.} + +\item{...}{Other named arguments that will be treated as resource properties.} + +\item{name}{For \code{subnet_config}, the name of the subnet. Duplicate names will automatically be disambiguated prior to VM deployment.} + +\item{addresses}{For \code{subnet_config}, the address ranges spanned by this subnet. Must be a subset of the address space available to the parent virtual network.} + +\item{nsg}{The network security group associated with this subnet. Defaults to the NSG created as part of this VM deployment.} +} +\description{ +Virtual network configuration +} +\seealso{ +\link{create_vm}, \link{vm_config}, \link{vmss_config} +} diff --git a/tests/resources/testkey.pub b/tests/resources/testkey.pub new file mode 100644 index 0000000..2a9440b --- /dev/null +++ b/tests/resources/testkey.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSMJcL3SJ1knfBegs0r2UmHoHhux3rfxQLa1uv4PJUWqxhAOoGY3kbBt5qx9Hn8vNrUtGn06Brcsr/MQg3Iv7GYeh+EofJOX1ZewU7+vDR4UkGHBbl1xQDkeZdqNZ9RqbiB6V7mDaGjIlc+j5op2HxOMBU4DSKNbTu4CJCEGYZKf2ln+PIAv5FyHINTIOYRHCBvFERMOyxkTHclfkbcoedkRZILoOKVB3kbPwTREJpYG0co+nz6tkw45VTFXjBx75DSN2097NsReCNgdYM8R6eEqChsmDSmjsE43F0/pyM1F8tVDTSYbpVQn56Qaq9Ymbd6WOkhOSmPVIvmnKrymTV hongo@LAPTOP-P18OIHUP diff --git a/tests/testthat/test00_resconfig.R b/tests/testthat/test00_resconfig.R new file mode 100644 index 0000000..37bbdbd --- /dev/null +++ b/tests/testthat/test00_resconfig.R @@ -0,0 +1,175 @@ +context("Resource configs") + + +test_that("User config works", +{ + user <- user_config("username", sshkey="random key") + expect_is(user, "user_config") + expect_identical(user$key, "random key") + + user <- user_config("username", password="random password") + expect_is(user, "user_config") + expect_identical(user$pwd, "random password") + + user <- user_config("username", sshkey="../testthat.R") + expect_is(user, "user_config") + expect_identical(user$key, readLines("../testthat.R")) +}) + +test_that("Datadisk config works", +{ + disk <- datadisk_config(100) + expect_is(disk, "datadisk_config") + expect_identical(disk$res_spec$diskSizeGB, 100) + expect_identical(disk$vm_spec$createOption, "attach") + expect_identical(disk$vm_spec$caching, "None") + expect_null(disk$vm_spec$storageAccountType) +}) + +test_that("Image config works", +{ + expect_error(image_config()) + + img <- image_config(publisher="pubname", offer="offname", sku="skuname") + expect_is(img, "image_marketplace") + + img <- image_config(id="resource_id") + expect_is(img, "image_custom") +}) + +test_that("Network security group config works", +{ + nsg <- nsg_config() + expect_is(nsg, "nsg_config") + expect_identical(build_resource_fields(nsg), nsg_default) + + nsg <- nsg_config(list(nsg_rule_allow_ssh)) + expect_is(nsg, "nsg_config") + expect_is(nsg$properties$securityRules[[1]], "nsg_rule") + expect_identical(nsg$properties$securityRules[[1]]$name, "Allow-ssh") +}) + +test_that("Public IP address config works", +{ + ip <- ip_config() + expect_is(ip, "ip_config") + expect_null(ip$type) + expect_null(ip$dynamic) + + ip <- ip_config("static", FALSE) + expect_is(ip, "ip_config") + res <- build_resource_fields(ip) + expect_identical(res$properties, + list( + publicIPAllocationMethod="static", + publicIPAddressVersion="IPv4", + dnsSettings=list(domainNameLabel="[parameters('vmName')]") + ) + ) + expect_identical(res$sku, + list(name="static") + ) +}) + +test_that("Virtual network config works", +{ + vnet <- vnet_config() + expect_is(vnet, "vnet_config") + expect_is(vnet$properties$subnets[[1]], "subnet_config") + + res <- build_resource_fields(vnet) + expect_identical(res$properties, + list( + addressSpace=list(addressPrefixes=I("10.0.0.0/16")), + subnets=list( + list( + name="subnet", + properties=list( + addressPrefix="10.0.0.0/16", + networkSecurityGroup=list(id="[variables('nsgId')]") + ) + ) + ) + ) + ) +}) + +test_that("Network interface config works", +{ + nic <- nic_config() + expect_is(nic, "nic_config") + + res <- build_resource_fields(nic) + expect_identical(res$properties, + list( + ipConfigurations=list( + list( + name="ipconfig", + properties=list( + privateIPAllocationMethod="dynamic", + subnet=list(id="[variables('subnetId')]"), + publicIPAddress=list(id="[variables('ipId')]") + ) + ) + ) + ) + ) +}) + +test_that("Load balancer config works", +{ + lb <- lb_config() + expect_is(lb, "lb_config") + expect_null(lb$type) + + lb <- lb_config(type="basic") + res <- build_resource_fields(lb) + expect_identical(res$properties, + list( + frontendIPConfigurations=list( + list( + name="[variables('lbFrontendName')]", + properties=list( + publicIPAddress=list(id="[variables('ipId')]") + ) + ) + ), + backendAddressPools=list( + list( + name="[variables('lbBackendName')]" + ) + ), + loadBalancingRules=list(), + probes=list() + ) + ) + expect_identical(res$sku, + list(name="basic") + ) + + lb <- lb_config(type="basic", rules=list(lb_rule_ssh), probes=list(lb_probe_ssh)) + expect_is(lb$rules[[1]], "lb_rule") + expect_is(lb$probes[[1]], "lb_probe") + res <- build_resource_fields(lb) + expect_identical(res$properties$loadBalancingRules[[1]], unclass(lb_rule_ssh)) + expect_identical(res$properties$probes[[1]], unclass(lb_probe_ssh)) +}) + +test_that("Autoscaler config works", +{ + as <- autoscaler_config() + expect_is(as, "as_config") + expect_is(as$properties$profiles[[1]], "as_profile_config") + + res <- build_resource_fields(as) + expect_identical(res$properties, + list( + name="[variables('asName')]", + targetResourceUri="[variables('vmId')]", + enabled=TRUE, + profiles=list( + unclass(autoscaler_profile()) + ) + ) + ) +}) diff --git a/tests/testthat/test01_vm.R b/tests/testthat/test01_vm.R deleted file mode 100644 index e4e864d..0000000 --- a/tests/testthat/test01_vm.R +++ /dev/null @@ -1,115 +0,0 @@ -context("VM interface") - -tenant <- Sys.getenv("AZ_TEST_TENANT_ID") -app <- Sys.getenv("AZ_TEST_APP_ID") -password <- Sys.getenv("AZ_TEST_PASSWORD") -subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION") - -if(tenant == "" || app == "" || password == "" || subscription == "") - skip("Tests skipped: ARM credentials not set") - - -sub <- AzureRMR::az_rm$new(tenant=tenant, app=app, password=password)$get_subscription(subscription) - -rgname <- paste(sample(letters, 20, replace=TRUE), collapse="") -winvm_name <- paste0("win", paste0(sample(letters, 10, TRUE), collapse="")) -luxvm_name <- paste0("lux", paste0(sample(letters, 10, TRUE), collapse="")) -winvmrg_name <- paste0("winrg", paste0(sample(letters, 10, TRUE), collapse="")) -luxvmrg_name <- paste0("luxrg", paste0(sample(letters, 10, TRUE), collapse="")) -winvmclus_name <- paste0("wincl", paste0(sample(letters, 10, TRUE), collapse="")) -luxvmclus_name <- paste0("luxcl", paste0(sample(letters, 10, TRUE), collapse="")) - - -test_that("VM creation works", -{ - expect_is(sub$create_vm(winvm_name, location="australiaeast", - username="ds", pass="PassWord343!"), - "az_vm_template") - expect_true(sub$resource_group_exists(winvm_name)) - - expect_is(sub$create_vm(luxvm_name, location="australiaeast", - os="Ubuntu", username="ds", pass=readLines("~/.ssh/id_rsa.pub"), userauth_type="key"), - "az_vm_template") - expect_true(sub$resource_group_exists(luxvm_name)) - - rg <- sub$create_resource_group(rgname, location="australiaeast") - - expect_is(rg$create_vm(winvmrg_name, - username="ds", pass="PassWord343!"), - "az_vm_template") - expect_is(rg$create_vm(luxvmrg_name, - os="Ubuntu", username="ds", pass=readLines("~/.ssh/id_rsa.pub"), userauth_type="key"), - "az_vm_template") -}) - -test_that("VM interaction works", -{ - winvm <- sub$get_vm(winvm_name) - expect_is(winvm, "az_vm_template") - - expect_silent(winvm$run_script("dir \\")) - - winvm$stop(deallocate=FALSE, wait=TRUE) - expect_true(winvm$status[[1]]["PowerState"] == "stopped") - - winvm$resize("Standard_DS2_v2", wait=TRUE) - expect_true(winvm$.__enclos_env__$private$vm[[1]]$properties$hardwareProfile == "Standard_DS2_v2") - - winvm$start(wait=TRUE) - expect_true(winvm$status[[1]]["PowerState"] == "running") - - luxvm <- sub$get_vm(luxvm_name) - expect_is(luxvm, "az_vm_template") - - expect_silent(luxvm$run_script("ls -al /")) - - luxvm$stop(deallocate=FALSE, wait=TRUE) - expect_true(luxvm$status[[1]]["PowerState"] == "stopped") - - luxvm$resize("Standard_DS2_v2", wait=TRUE) - expect_true(luxvm$.__enclos_env__$private$vm[[1]]$properties$hardwareProfile == "Standard_DS2_v2") - - luxvm$start(wait=TRUE) - expect_true(luxvm$status[[1]]["PowerState"] == "running") -}) - -test_that("VM deletion works", -{ - verify_rg_deleted <- function(rgname) - { - for(i in 1:100) - { - Sys.sleep(5) - if(!sub$resource_group_exists(rgname)) - break - } - expect_false(sub$resource_group_exists(rgname)) - } - - winvm <- sub$get_vm(winvm_name) - expect_is(winvm, "az_vm_template") - winvm$delete(confirm=FALSE) - verify_rg_deleted(winvm_name) - - luxvm <- sub$get_vm(luxvm_name) - expect_is(luxvm, "az_vm_template") - luxvm$delete(confirm=FALSE) - verify_rg_deleted(luxvm_name) - - rg <- sub$get_resource_group(rgname) - - winvmrg <- rg$get_vm(winvmrg_name) - expect_is(winvmrg, "az_vm_template") - winvmrg$delete(confirm=FALSE) - - luxvmrg <- rg$get_vm(luxvmrg_name) - expect_is(luxvmrg, "az_vm_template") - luxvmrg$delete(confirm=FALSE) - - Sys.sleep(10) - expect_true(is_empty(rg$list_resources())) -}) - - -sub$delete_resource_group(rgname, confirm=FALSE) - diff --git a/tests/testthat/test01_vmconfig.R b/tests/testthat/test01_vmconfig.R new file mode 100644 index 0000000..0961ee4 --- /dev/null +++ b/tests/testthat/test01_vmconfig.R @@ -0,0 +1,170 @@ +context("VM+scaleset config") + + +test_that("VM config works", +{ + key_user <- user_config("username", ssh="random key") + pwd_user <- user_config("username", password="random password") + img <- image_config(publisher="pubname", offer="offname", sku="skuname") + vm <- vm_config(img, keylogin=TRUE) + expect_is(vm, "vm_config") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", key_user, "size")) + + vm <- ubuntu_18.04() + expect_is(vm, "vm_config") + expect_true(vm$image$publisher == "Canonical" && + vm$image$offer == "UbuntuServer" && + vm$image$sku == "18.04-LTS") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", key_user, "size")) + + vm <- ubuntu_16.04() + expect_is(vm, "vm_config") + expect_true(vm$image$publisher == "Canonical" && + vm$image$offer == "UbuntuServer" && + vm$image$sku == "16.04-LTS") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", key_user, "size")) + + vm <- windows_2019() + expect_is(vm, "vm_config") + expect_true(vm$image$publisher == "MicrosoftWindowsServer" && + vm$image$offer == "WindowsServer" && + vm$image$sku == "2019-Datacenter") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", pwd_user, "size")) + + vm <- windows_2016() + expect_is(vm, "vm_config") + expect_true(vm$image$publisher == "MicrosoftWindowsServer" && + vm$image$offer == "WindowsServer" && + vm$image$sku == "2016-Datacenter") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", pwd_user, "size")) + + vm <- rhel_8() + expect_is(vm, "vm_config") + expect_true(vm$image$publisher == "RedHat" && + vm$image$offer == "RHEL" && + vm$image$sku == "8") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", key_user, "size")) + + vm <- rhel_7.6() + expect_is(vm, "vm_config") + expect_true(vm$image$publisher == "RedHat" && + vm$image$offer == "RHEL" && + vm$image$sku == "7-RAW") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", key_user, "size")) + + vm <- debian_9_backports() + expect_is(vm, "vm_config") + expect_true(vm$image$publisher == "Credativ" && + vm$image$offer == "Debian" && + vm$image$sku == "9-backports") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", key_user, "size")) + + vm <- ubuntu_dsvm() + expect_is(vm, "vm_config") + expect_true(vm$image$publisher == "microsoft-dsvm" && + vm$image$offer == "linux-data-science-vm-ubuntu" && + vm$image$sku == "linuxdsvmubuntu") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", key_user, "size")) + + vm <- windows_dsvm() + expect_is(vm, "vm_config") + expect_true(vm$image$publisher == "microsoft-dsvm" && + vm$image$offer == "dsvm-windows" && + vm$image$sku == "server-2016") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", pwd_user, "size")) +}) + + +test_that("VM scaleset config works", +{ + key_user <- user_config("username", ssh="random key") + pwd_user <- user_config("username", password="random password") + img <- image_config(publisher="pubname", offer="offname", sku="skuname") + vm <- vmss_config(img, keylogin=TRUE) + expect_is(vm, "vmss_config") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", key_user, "size", 5)) + + vm <- ubuntu_18.04_ss() + expect_is(vm, "vmss_config") + expect_true(vm$image$publisher == "Canonical" && + vm$image$offer == "UbuntuServer" && + vm$image$sku == "18.04-LTS") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", key_user, "size", 5)) + + vm <- ubuntu_16.04_ss() + expect_is(vm, "vmss_config") + expect_true(vm$image$publisher == "Canonical" && + vm$image$offer == "UbuntuServer" && + vm$image$sku == "16.04-LTS") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", key_user, "size", 5)) + + vm <- windows_2019_ss() + expect_is(vm, "vmss_config") + expect_true(vm$image$publisher == "MicrosoftWindowsServer" && + vm$image$offer == "WindowsServer" && + vm$image$sku == "2019-Datacenter") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", pwd_user, "size", 5)) + + vm <- windows_2016_ss() + expect_is(vm, "vmss_config") + expect_true(vm$image$publisher == "MicrosoftWindowsServer" && + vm$image$offer == "WindowsServer" && + vm$image$sku == "2016-Datacenter") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", pwd_user, "size", 5)) + + vm <- rhel_8_ss() + expect_is(vm, "vmss_config") + expect_true(vm$image$publisher == "RedHat" && + vm$image$offer == "RHEL" && + vm$image$sku == "8") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", key_user, "size", 5)) + + vm <- rhel_7.6_ss() + expect_is(vm, "vmss_config") + expect_true(vm$image$publisher == "RedHat" && + vm$image$offer == "RHEL" && + vm$image$sku == "7-RAW") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", key_user, "size", 5)) + + vm <- debian_9_backports_ss() + expect_is(vm, "vmss_config") + expect_true(vm$image$publisher == "Credativ" && + vm$image$offer == "Debian" && + vm$image$sku == "9-backports") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", key_user, "size", 5)) + + vm <- ubuntu_dsvm_ss() + expect_is(vm, "vmss_config") + expect_true(vm$image$publisher == "microsoft-dsvm" && + vm$image$offer == "linux-data-science-vm-ubuntu" && + vm$image$sku == "linuxdsvmubuntu") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", key_user, "size", 5)) + + vm <- windows_dsvm_ss() + expect_is(vm, "vmss_config") + expect_true(vm$image$publisher == "microsoft-dsvm" && + vm$image$offer == "dsvm-windows" && + vm$image$sku == "server-2016") + expect_silent(build_template_definition(vm)) + expect_silent(build_template_parameters(vm, "vmname", pwd_user, "size", 5)) +}) + diff --git a/tests/testthat/test10_vm.R b/tests/testthat/test10_vm.R new file mode 100644 index 0000000..61a1161 --- /dev/null +++ b/tests/testthat/test10_vm.R @@ -0,0 +1,57 @@ +context("VM interface") + +tenant <- Sys.getenv("AZ_TEST_TENANT_ID") +app <- Sys.getenv("AZ_TEST_APP_ID") +password <- Sys.getenv("AZ_TEST_PASSWORD") +subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION") + +if(tenant == "" || app == "" || password == "" || subscription == "") + skip("Tests skipped: ARM credentials not set") + +vm_name <- paste0("vm", paste0(sample(letters, 10, TRUE), collapse="")) +location <- "australiaeast" + +rg <- AzureRMR::az_rm$ + new(tenant=tenant, app=app, password=password)$ + get_subscription(subscription)$ + create_resource_group(vm_name, location) + +test_that("VM creation works", +{ + vm <- rg$create_vm(vm_name, user_config("username", "../resources/testkey.pub"), "Standard_DS1_v2") + expect_is(vm, "az_vm_template") +}) + +test_that("VM interaction works", +{ + vm <- rg$get_vm(vm_name) + expect_is(vm, "az_vm_template") + + expect_silent(vm$run_script("ls /tmp")) + + vm$stop(deallocate=FALSE, wait=TRUE) + status <- vm$sync_vm_status() + expect_true(status["PowerState"] == "stopped") + + vm$resize("Standard_DS2_v2", wait=TRUE) + expect_true(vm$.__enclos_env__$private$vm$properties$hardwareProfile == "Standard_DS2_v2") + + vm$start(wait=TRUE) + status <- vm$sync_vm_status() + expect_true(status["PowerState"] == "running") + + expect_is(vm$get_public_ip_address(), "character") + expect_is(vm$get_private_ip_address(), "character") +}) + +test_that("VM deletion works", +{ + vm <- rg$get_vm(vm_name) + vm$delete(confirm=FALSE) + + Sys.sleep(10) + expect_true(is_empty(rg$list_resources())) +}) + +rg$delete(confirm=FALSE) + diff --git a/tests/testthat/test11_vmss.R b/tests/testthat/test11_vmss.R new file mode 100644 index 0000000..6ffbb05 --- /dev/null +++ b/tests/testthat/test11_vmss.R @@ -0,0 +1,46 @@ +context("VM scaleset interface") + +tenant <- Sys.getenv("AZ_TEST_TENANT_ID") +app <- Sys.getenv("AZ_TEST_APP_ID") +password <- Sys.getenv("AZ_TEST_PASSWORD") +subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION") + +if(tenant == "" || app == "" || password == "" || subscription == "") + skip("Tests skipped: ARM credentials not set") + +vmss_name <- paste0("vmss", paste0(sample(letters, 10, TRUE), collapse="")) +location <- "australiaeast" + +rg <- AzureRMR::az_rm$ + new(tenant=tenant, app=app, password=password)$ + get_subscription(subscription)$ + create_resource_group(vmss_name, location) + +test_that("Scaleset creation works", +{ + vm <- rg$create_vm_scaleset(vmss_name, user_config("username", "../resources/testkey.pub"), instances=2) + expect_is(vm, "az_vmss_template") +}) + +test_that("Scaleset interaction works", +{ + vm <- rg$get_vm_scaleset(vmss_name) + expect_is(vm, "az_vmss_template") + + expect_silent(vm$run_script("ls /tmp")) + + expect_is(vm$get_vm_private_ip_addresses(), "character") + expect_is(vm$get_vm_public_ip_addresses(), "character") + expect_is(vm$get_public_ip_address(), "character") +}) + +test_that("Scaleset deletion works", +{ + vm <- rg$get_vm_scaleset(vmss_name) + vm$delete(confirm=FALSE) + + Sys.sleep(10) + expect_true(is_empty(rg$list_resources())) +}) + +rg$delete(confirm=FALSE) diff --git a/vignettes/intro.rmd b/vignettes/intro.rmd index d428d61..0dbc876 100644 --- a/vignettes/intro.rmd +++ b/vignettes/intro.rmd @@ -8,157 +8,164 @@ vignette: > %\VignetteEncoding{utf8} --- -This is a short introduction on how to use AzureVM. +AzureVM is a package for interacting with virtual machines and virtual machine scalesets in Azure. You can deploy, start up, shut down, run scripts, deallocate and delete VMs and scalesets from the R command line. It uses the tools provided by the [AzureRMR package](https://github.com/Azure/AzureRMR) to manage VM resources and templates. +## Virtual machines -## Creating a VM +Here is a simple example. We create a VM using the default settings, run a shell command, and then delete the VM. -Creating a VM is as simple as using the `create_vm` method, which is available as part of the `az_subscription` and `az_resource_group` classes. +```r +library(AzureVM) -```{r, eval=FALSE} -## using the subscription method -sub <- az_rm$ - new(tenant="{tenant_id}", app="{app_id}", password="{password}")$ - get_subscription("{subscription_id}") +sub <- AzureRMR::get_azure_login()$ + get_subscription("5710aa44-281f-49fe-bfa6-69e66bb55b11") -myNewVM <- sub$create_vm("myNewVM", - location="australiaeast", - username="datascience", - passkey="fAs30q-2a5vF!Z") # be sure to choose a strong password! +# calling create_vm() from a subscription object will create the VM in its own resource group +# default is an Ubuntu 18.04 VM, size Standard_DS3_v2, login via SSH key +# call sub$list_vm_sizes() to get the sizes available in your region +vm <- sub$create_vm("myubuntuvm", user_config("myname", "~/.ssh/id_rsa.pub"), + location="australiaeast") +# run a shell script or command remotely (will be PowerShell on a Windows VM) +vm$run_script("ifconfig > /tmp/ifc.txt") -## using the resource group method -rg <- sub$create_resource_group("myresourcegroup", - location="australiaeast") +# ... and stop it +vm$stop() -myOtherVM <- rg$create_vm("myOtherVM", - username="datascience", - passkey="l3Kgrf21%?0DFm") -``` +# ... and resize it +vm$resize("Standard_DS4_v2") -Without any other options, this will create a Windows Server 2016 [Data Science Virtual Machine](https://docs.microsoft.com/en-us/azure/machine-learning/data-science-virtual-machine/overview#whats-included-in-the-data-science-vm), which is pre-installed with several tools useful for analytics: Python, R (and RStudio), Tensorflow, XGBoost, SQL Server, and so on. The size will be a Standard DS3 V2 VM, which has 4 cores, 14GB of memory, 1TB primary disk, and up to 16x28GB data disks. - -A feature of using the `az_subscription` method is that it creates a new resource group specifically to hold the VM. This simplifies the task of managing and (eventually) deleting the VM considerably. See "Deleting a VM" below. - -You can change the specifications for the VM by providing any of the following arguments: - -- `os`: either "Windows" or "Ubuntu" (for Ubuntu LTS 16.04). -- `size`: the VM size. Use the `az_subscription$list_vm_sizes()` method to see what sizes are available in your region. Note that in Azure, a VM's size is really a broad-ranging label that encapsulates the number of cores, memory, and disk available. -- `passkey`: if creating an Ubuntu VM, you can supply a public key as the `passkey` argument. -- `userauth_type`: set this to "key" if you supply a public key. - -Setting these will determine whether you get a Windows or Ubuntu DSVM, the login details, and how powerful the VM is in terms of cores and memory/disk capacity. - -```{r, eval=FALSE} -# examine VM sizes available in australiaeast region -sub$list_vm_sizes("australiaeast") - -# create a Linux (Ubuntu) NC-series DSVM using public key authentication -myLinuxVM <- sub$create_vm("myLinuxVM", - location="australiaeast", - size="Standard_NC6s_v3", - os="Ubuntu", - username="datascience", - passkey=readLines("~/id_rsa.pub"), - userauth_type="key") -``` - - -## Retrieving an existing VM - -If you have an existing VM, you can retrieve it with the `get_vm()` method. As with `create_vm()`, this is available as part of the `az_subscription` and `az_resource_group` classes. The only argument you need to supply is the name of the VM. - -```{r, eval=FALSE} -## using the subscription method -sub <- az_rm$ - new(tenant="{tenant_id}", app="{app_id}", password="{password}")$ - get_subscription("{subscription_id}") - -# retrieve the VM we created above -myNewVM <- sub$get_vm("myNewVM") - - -## and with the resource group method -rg <- sub$get_resource_group("myresourcegroup") - -myOtherVM <- rg$get_vm("myOtherVM") -``` - - -## Working with a VM - -There are various things you can do with a VM object. - -To stop (shutdown) a VM, call its `stop()` method. The `deallocate` argument sets whether to deallocate its resources as well, with the default being TRUE; you may want to set this to FALSE if you know you will be restarting the VM soon. To restart it, call either the `start()` or `restart()` method. The main difference between the two is that `restart()` will shutdown the VM first if it is currently running. - -To sync the object with the resource in Azure, call the `sync_vm_status()` method. The most common situation where you might want to do this is if you create a VM with the argument `wait=FALSE`. In this case, rather than waiting for provisioning to complete, the `create_vm` method will return an incomplete VM object; you then call its `sync_vm_status()` method to update it with how the provisioning is going in Azure. - -To run a script in the VM (without manually logging in first), call the `run_script()` method. This will be a PowerShell script if it is a Windows VM, or a bash script if it is Linux. The script is just a character vector: - -```{r, eval=FALSE} -# simple bash script for executing on a Linux VM -script <- -'#!/bin/bash - -var=Hello world! - -# redirect output to a file so we know whether it ran successfully -echo "$var" > /tmp/helloworld.txt -' - -vm$run_script(script) -``` - -If you login to the VM after running this script, you should find the file `helloworld.txt` in the `/tmp` directory. - - -## Deleting a VM - -Simply deleting a virtual machine object in R, eg with `rm()`, will not do anything to the VM itself in Azure. To delete the VM and its resources, call the object's `delete()` method: - -```{r, eval=FALSE} +# ... and delete it (this can be done asynchronously for a VM in its own group) vm$delete() ``` -AzureVM will prompt you for confirmation that you really want to delete the VM. By default, this will also free up all the individual Azure resources used by the VM, such as its storage, network interface, security group, and so on. +AzureVM comes with a number of predefined configurations, for deploying commonly used VM images. For example, to create an Ubuntu DSVM accessible via SSH, JupyterHub and RStudio Server: -If you created the VM using the `create_vm()` method of the `az_subscription` class, the deletion process is very simple: it simply removes the resource group containing the VM. This is possible because the resource group was created as part of deploying the VM. Be aware that any other resources you may have created in this resource group will also be deleted. +```r +sub$create_vm("mydsvm", user_config("myname", "~/.ssh/id_rsa.pub"), config="ubuntu_dsvm", + location="australiaeast") +``` +And to create a Windows Server 2019 VM, accessible via RDP: - Alternatively, you can use the `delete_vm()` method of the `az_subscription` and `az_resource_group` classes. These will retrieve the VM of the given name and then call its `delete()` method. - - -## Other topics - -### GPU enabled VMs - -If you are running deep learning workloads, you'll want to ensure that your VM is GPU-enabled. In Azure, the various [NC- and ND-series VMs](https://docs.microsoft.com/en-us/azure/virtual-machines/windows/sizes-gpu) are designed for these workloads. You can create a GPU-enabled VM by setting the `size` argument appropriately, for example - -```{r, eval=FALSE} -myGpuVM <- sub$create_vm(size="Standard_NC12s_v3", ...) +```r +sub$create_vm("mywinvm", user_config("myname", password="Use-strong-passwords!"), config="windows_2019", + location="australiaeast") ``` -However, the following caveats apply to GPU-enabled VMs: -- Not all regions have GPUs available. To check on availability, use the `az_subscription$list_vm_sizes()` method and provide your region. -- Currently, the supply of GPUs is limited. You must [apply for a quota increase](https://docs.microsoft.com/en-us/azure/azure-supportability/resource-manager-core-quotas-request) if you want to deploy a GPU-enabled VM. +The available predefined configurations are `ubuntu_18.04` (the default), `ubuntu_16.04`, `ubuntu_dsvm`, `windows_2019`, `windows_2016`, `windows_dsvm`, `rhel_7.6`, `rhel_8` and `debian_9_backports`. You can combine these with several other arguments to customise the VM deployment to your needs: -### VM clusters +- `size`: VM size. Use the `list_vm_sizes` method for the subscription and resource group classes to see the available sizes. +- `datadisks`: The data disk sizes/configurations to attach. +- `ip`: Public ip address. Set this to NULL if you don't want the VM to be accessible outside its subnet. +- `vnet`: Virtual network/subnet. +- `nsg`: Network security group. AzureVM will associate the NSG with the vnet/subnet, not with the VM's network interface. +- `nic`: Network interface. +- `other_resources`: Optionally, a list of other resources to deploy. -You can work with VM clusters (a collection of VMs sharing the same configuration) by using the `get_vm_cluster`, `create_vm_cluster` and `delete_vm_cluster` methods. These work almost identically to `get_vm`, `create_vm` and `delete_vm` with the addition of a `clust_size` argument that sets the size of the cluster. +```r +# Windows Server 2016, with a 500GB datadisk attached, not publicly accessible +sub$create_vm("mywinvm2", user_config("myname", password="Use-strong-passwords!"), + size="Standard_DS4_v2", config="windows_2016", datadisks=500, ip=NULL, + location="australiaeast") -```{r, eval=FALSE} -sub <- az_rm$ - new(tenant="{tenant_id}", app="{app_id}", password="{password}")$ - get_subscription("{subscription_id}") +# Ubuntu DSVM, GPU-enabled +sub$create_vm("mydsvm", user_config("myname", "~/.ssh/id_rsa.pub"), size="Standard_NC12", + config="ubuntu_dsvm_ss", + location="australiaeast") -# create a cluster of 5 Ubuntu VMs -vmCluster <- sub$create_vm_cluster("vmCluster", - location="australiaeast", - os="Ubuntu", - username="datascience", - passkey=readLines("~/id_rsa.pub"), - userauth_type="key", - clust_size=5) +# Red Hat VM, serving HTTP/HTTPS +sub$create_vm("myrhvm", user_config("myname", "~/.ssh/id_rsa.pub"), config="rhel_8", + nsg=nsg_config(list(nsg_rule_allow_http, nsg_rule_allow_https)), + location="australiaeast") ``` -Most things that you can do with a single VM, you can also do with a VM cluster. For example, running a script with the `run_script()` method will run the script on all the VMs in the cluster. Starting, stopping and restarting a cluster similarly carries out the given action on all the VMs. +Full customisation is provided by the `vm_config` function, which also lets you specify the image to deploy, either from the marketplace or a disk. (The predefined configurations actually call `vm_config`, with the appropriate arguments for each specific config.) + +```r +## custom VM configuration: Windows 10 Pro 1903 with data disks +## this assumes you have a valid Win10 desktop license +user <- user_config("myname", password="Use-strong-passwords!") +image <- image_config( + publisher="MicrosoftWindowsDesktop", + offer="Windows-10", + sku="19h1-pro" +) +datadisks <- list( + datadisk_config(250, type="Premium_LRS"), + datadisk_config(1000, type="Standard_LRS") +) +nsg <- nsg_config( + list(nsg_rule_allow_rdp) +) +sub$create_vm("mywin10vm", user, + config=vm_config( + image=image, + keylogin=FALSE, + datadisks=datadisks, + nsg=nsg, + properties=list(licenseType="Windows_Client") + ), + location="australiaeast" +) +``` + +## VM scalesets + +The equivalent to `create_vm` for scalesets is the `create_vm_scaleset` method. By default, a new scaleset will come with a load balancer and autoscaler attached, but its instances will not be externally accessible. + +```r +# default is Ubuntu 18.04 scaleset, size Standard_DS1_v2 +sub$create_vm_scaleset("myubuntuss", user_config("myname", "~/.ssh/id_rsa.pub"), instances=5, + location="australiaeast") +``` + +Each predefined VM configuration has a corresponding scaleset configuration. To specify low-level scaleset settings, use the `scaleset_options` argument. Here are some sample scaleset deployments: + +```r +# Windows Server 2019 +sub$create_vm_scaleset("mywinss", user_config("myname", password="Use-strong-passwords!"), instances=5, + config="windows_2019", + location="australiaeast") + +# RHEL scaleset, serving HTTP/HTTPS +sub$create_vm_scaleset("myrhelss", user_config("myname", "~/.ssh/id_rsa.pub"), instances=5, + config="rhel_8_ss", + nsg=nsg_config(list(nsg_rule_allow_http, nsg_rule_allow_https)), + location="australiaeast") + +# Ubuntu DSVM, GPU-enabled, public instances, no load balancer or autoscaler +sub$create_vm_scaleset("mydsvmss", user_config("myname", "~/.ssh/id_rsa.pub"), instances=5, + size="Standard_NC6", config="ubuntu_dsvm_ss", + options=scaleset_options(public=TRUE), + load_balancer=NULL, autoscaler=NULL, + location="australiaeast") + +# Large Debian scaleset (multiple placement groups), using low-priority VMs +# need to set the instance size to something that supports low-pri +sub$create_vm_scaleset("mydebss", user_config("myname", "~/.ssh/id_rsa.pub"), instances=10, + size="Standard_DS3_v2", config="debian_9_backports_ss", + options=scaleset_options(low_priority=TRUE, large_scaleset=TRUE), + location="australiaeast") +``` + +## Sharing resources + +You can also include an existing Azure resource in a deployment, by supplying an AzureRMR `az_resource` object as an argument in the `create_vm` or `create_vm_scaleset` call. For example, here we create a VM and a scaleset that share a single virtual network/subnet. + +```r +## VM and scaleset in the same resource group and virtual network +# first, create the resgroup +rg <- sub$create_resource_group("rgname", "australiaeast") + +# create the master +rg$create_vm("mastervm", user_config("myname", "~/.ssh/id_rsa.pub")) + +# get the vnet resource +vnet <- rg$get_resource(type="Microsoft.Network/virtualNetworks", name="mastervm-vnet") + +# create the scaleset +# since the NSG is associated with the vnet, we don't need to create a new NSG either +rg$create_vm_scaleset("slavess", user_config("myname", "~/.ssh/id_rsa.pub"), + instances=5, vnet=vnet, nsg=NULL, load_balancer=NULL, autoscaler=NULL) +```