diff --git a/.github/workflows/check-standard.yaml b/.github/workflows/check-standard.yaml index d2dfb7e..5a5d308 100644 --- a/.github/workflows/check-standard.yaml +++ b/.github/workflows/check-standard.yaml @@ -6,7 +6,7 @@ name: R-CMD-check jobs: R-CMD-check: - if: github.repository_owner == 'Azure' + if: github.repository_owner != 'cloudyr' runs-on: ${{ matrix.config.os }} name: ${{ matrix.config.os }} (${{ matrix.config.r }}) @@ -29,7 +29,7 @@ jobs: fetch-depth: 0 # required for mirroring, see https://stackoverflow.com/a/64272409/474349 - name: Copy to Cloudyr - if: runner.os == 'Linux' && github.ref == 'refs/heads/master' + if: runner.os == 'Linux' && github.ref == 'refs/heads/master' && github.repository_owner == 'Azure' env: token: "${{ secrets.ghPat }}" # git config hack required, see https://stackoverflow.com/q/64270867/474349 @@ -91,7 +91,7 @@ jobs: path: check - name: Update Cloudyr drat - if: success() && runner.os == 'Linux' && github.ref == 'refs/heads/master' + if: success() && runner.os == 'Linux' && github.ref == 'refs/heads/master' && github.repository_owner == 'Azure' env: token: "${{ secrets.ghPat }}" run: | diff --git a/DESCRIPTION b/DESCRIPTION index df2fc36..fecabda 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: AzureRMR Title: Interface to 'Azure Resource Manager' -Version: 2.3.6 +Version: 2.3.6.9000 Authors@R: c( person("Hong", "Ooi", , "hongooi73@gmail.com", role = c("aut", "cre")), person("Microsoft", role="cph") @@ -13,7 +13,7 @@ VignetteBuilder: knitr Depends: R (>= 3.3) Imports: - AzureGraph (>= 1.0.4), + AzureGraph (>= 1.1.2.9000), AzureAuth (>= 1.2.1), utils, parallel, @@ -29,3 +29,5 @@ Suggests: AzureStor Roxygen: list(markdown=TRUE, r6=FALSE, old_usage=TRUE) RoxygenNote: 7.1.1 +Remotes: + Azure/AzureGraph diff --git a/LICENSE b/LICENSE index bbfe484..01e64ba 100644 --- a/LICENSE +++ b/LICENSE @@ -1,2 +1,2 @@ -YEAR: 2018 +YEAR: 2018-2021 COPYRIGHT HOLDER: Microsoft diff --git a/NAMESPACE b/NAMESPACE index b82df03..593d2fb 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -49,4 +49,5 @@ export(pool_map) export(pool_sapply) export(pool_size) import(AzureAuth) +import(AzureGraph) importFrom(utils,modifyList) diff --git a/NEWS.md b/NEWS.md index 3e7dfb6..6b753d9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,10 @@ +# AzureRMR 2.3.6.9000 + +- Some utility functions moved to AzureGraph package. These are imported and then reexported by AzureRMR so that existing code should work unchanged. +- Add methods to get, create and delete sub-resources of a resource, eg `res$get_subresource(type="subrestype", name="subresname")`. See `az_resource` for more information. +- Fix a bug in obtaining the Microsoft Graph login when using AAD v2.0. +- Switch to AAD v2.0 as the default for authenticating. + # AzureRMR 2.3.6 - Add ability to specify user-defined functions in `build_template_definition`. Also allow changing the schema, content version and api profile. diff --git a/R/AzureRMR.R b/R/AzureRMR.R index 6a38b55..81c4689 100644 --- a/R/AzureRMR.R +++ b/R/AzureRMR.R @@ -1,7 +1,20 @@ #' @import AzureAuth +#' @import AzureGraph #' @importFrom utils modifyList NULL +#' @export +AzureGraph::named_list + +#' @export +AzureGraph::is_empty + +#' @export +AzureGraph::format_public_fields + +#' @export +AzureGraph::format_public_methods + utils::globalVariables(c("self", "private", "pool")) .onLoad <- function(libname, pkgname) diff --git a/R/az_login.R b/R/az_login.R index 5fd9a5d..be632d2 100644 --- a/R/az_login.R +++ b/R/az_login.R @@ -8,6 +8,8 @@ #' @param auth_type The OAuth authentication method to use, one of "client_credentials", "authorization_code", "device_code" or "resource_owner". If `NULL`, this is chosen based on the presence of the `username` and `password` arguments. #' @param host Your ARM host. Defaults to `https://management.azure.com/`. Change this if you are using a government or private cloud. #' @param aad_host Azure Active Directory host for authentication. Defaults to `https://login.microsoftonline.com/`. Change this if you are using a government or private cloud. +#' @param version The Azure Active Directory version to use for authenticating. +#' @param scopes The Azure Service Management scopes (permissions) to obtain for this login. Only for `version=2`. #' @param config_file Optionally, a JSON file containing any of the arguments listed above. Arguments supplied in this file take priority over those supplied on the command line. You can also use the output from the Azure CLI `az ad sp create-for-rbac` command. #' @param token Optionally, an OAuth 2.0 token, of class [AzureToken]. This allows you to reuse the authentication details for an existing session. If supplied, the other arguments above to `create_azure_login` will be ignored. #' @param graph_host The Microsoft Graph endpoint. See 'Microsoft Graph integration' below. @@ -66,9 +68,10 @@ #' @rdname azure_login #' @export create_azure_login <- function(tenant="common", app=.az_cli_app_id, - password=NULL, username=NULL, certificate=NULL, auth_type=NULL, + password=NULL, username=NULL, certificate=NULL, auth_type=NULL, version=2, host="https://management.azure.com/", aad_host="https://login.microsoftonline.com/", - config_file=NULL, token=NULL, graph_host="https://graph.microsoft.com/", ...) + scopes=".default", config_file=NULL, token=NULL, + graph_host="https://graph.microsoft.com/", ...) { if(!is_azure_token(token)) { @@ -84,7 +87,11 @@ create_azure_login <- function(tenant="common", app=.az_cli_app_id, tenant <- normalize_tenant(tenant) app <- normalize_guid(app) - token_args <- list(resource=host, + newhost <- if(version == 2) + c(paste0(host, scopes), "openid", "offline_access") + else host + + token_args <- list(resource=newhost, tenant=tenant, app=app, password=password, @@ -92,6 +99,7 @@ create_azure_login <- function(tenant="common", app=.az_cli_app_id, certificate=certificate, auth_type=auth_type, aad_host=aad_host, + version=version, ...) hash <- do.call(token_hash, token_args) @@ -114,7 +122,7 @@ create_azure_login <- function(tenant="common", app=.az_cli_app_id, arm_logins[[tenant]] <- sort(unique(c(arm_logins[[tenant]], client$token$hash()))) save_arm_logins(arm_logins) - make_graph_login_from_token(token, aad_host, graph_host) + make_graph_login_from_token(token, host, graph_host) client } diff --git a/R/az_resource.R b/R/az_resource.R index 1c269e8..40c58db 100644 --- a/R/az_resource.R +++ b/R/az_resource.R @@ -10,6 +10,9 @@ #' - `sync_fields()`: Synchronise the R object with the resource it represents in Azure. Returns the `properties$provisioningState` field, so you can query this programmatically to check if a resource has finished provisioning. Not all resource types require explicit provisioning, in which case this method will return NULL. #' - `set_api_version(api_version, stable_only=TRUE)`: Set the API version to use when interacting with the host. If `api_version` is not supplied, use the latest version available, either the latest stable version (if `stable_only=TRUE`) or the latest preview version (if `stable_only=FALSE`). #' - `get_api_version()`: Get the current API version. +#' - `get_subresource(type, name)`: Get a sub-resource of this resource. See 'Sub-resources' below. +#' - `create_subresource(type, name, ...)`: Create a sub-resource of this resource. +#' - `delete_subresource(type, name, confirm=TRUE)`: Delete a sub-resource of this resource. #' - `do_operation(...)`: Carry out an operation. See 'Operations' for more details. #' - `set_tags(..., keep_existing=TRUE)`: Set the tags on this resource. The tags can be either names or name-value pairs. To delete a tag, set it to `NULL`. #' - `get_tags()`: Get the tags on this resource. @@ -54,6 +57,17 @@ #' #' Consult the Azure documentation for your resource to find out what operations are supported. #' +#' @section Sub-resources: +#' Some resource types can have sub-resources: objects exposed by Resource Manager that make up a part of their parent's functionality. For example, a storage account (type `Microsoft.Storage/storageAccounts`) provides the blob storage service, which can be accessed via Resource Manager as a sub-resource of type `Microsoft.Storage/storageAccounts/blobServices/default`. +#' +#' To retrieve an existing sub-resource, use the `get_subresource()` method. You do not need to include the parent resource's type and name. For example, if `res` is a resource for a storage account, and you want to retrieve the sub-resource for the blob container "myblobs", call +#' +#' ``` +#' res$get_subresource(type="blobServices/default/containers", name="myblobs") +#' ``` +#' +#' Notice that the storage account's resource type and name are omitted from the `get_subresource` arguments. Similarly, to create a new subresource, call the `create_subresource()` method with the same naming convention, passing any required fields as named arguments; and to delete it, call `delete_subresource()`. +#' #' @section Role-based access control: #' AzureRMR implements a subset of the full RBAC functionality within Azure Active Directory. You can retrieve role definitions and add and remove role assignments, at the subscription, resource group and resource levels. See [rbac] for more information. #' @@ -106,7 +120,12 @@ #' # sync with Azure: useful to track resource creation/update status #' vm$sync_fields() #' -#' # delete a resource +#' ## subresource: create a public blob container +#' stor$create_subresource(type="blobservices/default/containers", name="mycontainer", +#' properties=list(publicAccess="container")) +#' +#' ## delete a subresource and resource +#' stor$delete_subresource(type="blobservices/default/containers", name="mycontainer") #' stor$delete() #' #' } @@ -234,6 +253,33 @@ public=list( invisible(NULL) }, + get_subresource=function(type, name, id, api_version=NULL) + { + name <- file.path(self$name, type, name) + az_resource$new(self$token, self$subscription, + resource_group=self$resource_group, type=self$type, name=name, id=id, + api_version=api_version) + }, + + create_subresource=function(type, name, id, location=self$location, ...) + { + name <- file.path(self$name, type, name) + az_resource$new(self$token, self$subscription, + resource_group=self$resource_group, type=self$type, name=name, id=id, + location=location, ...) + }, + + delete_subresource=function(type, name, id, api_version=NULL, confirm=TRUE, wait=FALSE) + { + name <- file.path(self$name, type, name) + # supply deployed_properties arg to prevent querying host for resource info + az_resource$ + new(self$token, self$subscription, self$resource_group, + type=self$type, name=name, id=id, + deployed_properties=list(NULL), api_version=api_version)$ + delete(confirm=confirm, wait=wait) + }, + do_operation=function(..., options=list(), http_verb="GET") { private$res_op(..., options=options, http_verb=http_verb) diff --git a/R/az_rm.R b/R/az_rm.R index d12e3d7..ec78240 100644 --- a/R/az_rm.R +++ b/R/az_rm.R @@ -22,9 +22,11 @@ #' - `username`: if `auth_type == "resource_owner"`, your username. #' - `certificate`: If `auth_type == "client_credentials", a certificate to authenticate with. This is a more secure alternative to using an app secret. #' - `auth_type`: The OAuth authentication method to use, one of "client_credentials", "authorization_code", "device_code" or "resource_owner". See [get_azure_token] for how the default method is chosen, along with some caveats. +#' - `version`: The Azure Active Directory version to use for authenticating. #' - `host`: your ARM host. Defaults to `https://management.azure.com/`. Change this if you are using a government or private cloud. #' - `aad_host`: Azure Active Directory host for authentication. Defaults to `https://login.microsoftonline.com/`. Change this if you are using a government or private cloud. #' - `...`: Further arguments to pass to `get_azure_token`. +#' - `scopes`: The Azure Service Management scopes (permissions) to obtain for this login. Only for `version=2`. #' - `token`: Optionally, an OAuth 2.0 token, of class [AzureToken]. This allows you to reuse the authentication details for an existing session. If supplied, all other arguments will be ignored. #' #' @section Operations: @@ -72,9 +74,9 @@ public=list( # authenticate and get subscriptions initialize=function(tenant="common", app=.az_cli_app_id, - password=NULL, username=NULL, certificate=NULL, auth_type=NULL, + password=NULL, username=NULL, certificate=NULL, auth_type=NULL, version=2, host="https://management.azure.com/", aad_host="https://login.microsoftonline.com/", - token=NULL, ...) + scopes=".default", token=NULL, ...) { if(is_azure_token(token)) { @@ -88,7 +90,10 @@ public=list( self$tenant <- normalize_tenant(tenant) app <- normalize_guid(app) - token_args <- list(resource=self$host, + if(version == 2) + host <- c(paste0(host, scopes), "openid", "offline_access") + + token_args <- list(resource=host, tenant=self$tenant, app=app, password=password, @@ -96,6 +101,7 @@ public=list( certificate=certificate, auth_type=auth_type, aad_host=aad_host, + version=version, ...) self$token <- do.call(get_azure_token, token_args) diff --git a/R/format.R b/R/format.R deleted file mode 100644 index 984345c..0000000 --- a/R/format.R +++ /dev/null @@ -1,65 +0,0 @@ -#' Format an Azure object -#' -#' Miscellaneous functions for printing Azure R6 objects -#' -#' @param env An R6 object's environment for printing. -#' @param exclude Objects in `env` to exclude from the printout. -#' -#' @details -#' These functions are utilities to aid in printing Azure R6 objects. They are not meant to be called by the user. -#' -#' @rdname format -#' @export -format_public_fields <- function(env, exclude=character(0)) -{ - objnames <- ls(env) - std_fields <- "token" - objnames <- setdiff(objnames, c(exclude, std_fields)) - - maxwidth <- as.integer(0.8 * getOption("width")) - - objconts <- sapply(objnames, function(n) - { - x <- get(n, env) - deparsed <- if(is_empty(x) || is.function(x)) # don't print empty fields - return(NULL) - else if(is.list(x)) - paste0("list(", paste(names(x), collapse=", "), ")") - else if(is.vector(x)) - { - x <- paste0(x, collapse=", ") - if(nchar(x) > maxwidth - nchar(n) - 10) - x <- paste0(substr(x, 1, maxwidth - nchar(n) - 10), " ...") - x - } - else deparse(x)[[1]] - - paste0(strwrap(paste0(n, ": ", deparsed), width=maxwidth, indent=2, exdent=4), - collapse="\n") - }, simplify=FALSE) - - empty <- sapply(objconts, is.null) - objconts <- objconts[!empty] - - # print etag at the bottom, not the top - if("etag" %in% names(objconts)) - objconts <- c(objconts[-which(names(objconts) == "etag")], objconts["etag"]) - - paste0(paste0(objconts, collapse="\n"), "\n---\n") -} - - -#' @rdname format -#' @export -format_public_methods <- function(env) -{ - objnames <- ls(env) - std_methods <- c("clone", "print", "initialize") - objnames <- setdiff(objnames, std_methods) - is_method <- sapply(objnames, function(obj) is.function(.subset2(env, obj))) - - maxwidth <- as.integer(0.8 * getOption("width")) - - objnames <- strwrap(paste(objnames[is_method], collapse=", "), width=maxwidth, indent=4, exdent=4) - paste0(" Methods:\n", paste0(objnames, collapse="\n"), "\n") -} diff --git a/R/make_graph_login.R b/R/make_graph_login.R index 0d56d5f..c05b301 100644 --- a/R/make_graph_login.R +++ b/R/make_graph_login.R @@ -1,4 +1,4 @@ -make_graph_login_from_token <- function(token, aad_host, graph_host) +make_graph_login_from_token <- function(token, azure_host, graph_host) { if(is_empty(graph_host)) return() @@ -7,7 +7,7 @@ make_graph_login_from_token <- function(token, aad_host, graph_host) newtoken <- token$clone() if(is_azure_v1_token(newtoken)) newtoken$resource <- graph_host - else newtoken$scope <- sub(aad_host, graph_host, newtoken$scope, fixed=TRUE) + else newtoken$scope <- c(paste0(graph_host, ".default"), "openid", "offline_access") newtoken$refresh() diff --git a/R/utils.R b/R/utils.R index 9265be0..970a20c 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1,51 +1,21 @@ #' Miscellaneous utility functions #' #' @param lst A named list of objects. -#' @param name_fields The components of the objects in `lst`, to be used as names. -#' @param x For `is_url` and `is_empty`, An R object. +#' @param x For `is_url`, An R object. #' @param https_only For `is_url`, whether to allow only HTTPS URLs. #' @param token For `get_paged_list`, an Azure OAuth token, of class [AzureToken]. #' @param next_link_name,value_name For `get_paged_list`, the names of the next link and value components in the `lst` argument. The default values are correct for Resource Manager. #' #' @details -#' `named_list` extracts from each object in `lst`, the components named by `name_fields`. It then constructs names for `lst` from these components, separated by a `"/"`. -#' #' `get_paged_list` reconstructs a complete list of objects from a paged response. Many Resource Manager list operations will return _paged_ output, that is, the response contains a subset of all items, along with a URL to query to retrieve the next subset. `get_paged_list` retrieves each subset and returns all items in a single list. #' #' @return -#' For `named_list`, the list that was passed in but with names. An empty input results in a _named list_ output: a list of length 0, with a `names` attribute. -#' #' For `get_paged_list`, a list. #' -#' For `is_url`, whether the object appears to be a URL (is character of length 1, and starts with the string `"http"`). Optionally, restricts the check to HTTPS URLs only. For `is_empty`, whether the length of the object is zero (this includes the special case of `NULL`). +#' For `is_url`, whether the object appears to be a URL (is character of length 1, and starts with the string `"http"`). Optionally, restricts the check to HTTPS URLs only. #' #' @rdname utils #' @export -named_list <- function(lst=NULL, name_fields="name") -{ - if(is_empty(lst)) - return(structure(list(), names=character(0))) - - lst_names <- sapply(name_fields, function(n) sapply(lst, `[[`, n)) - if(length(name_fields) > 1) - { - dim(lst_names) <- c(length(lst_names) / length(name_fields), length(name_fields)) - lst_names <- apply(lst_names, 1, function(nn) paste(nn, collapse="/")) - } - names(lst) <- lst_names - dups <- duplicated(tolower(names(lst))) - if(any(dups)) - { - duped_names <- names(lst)[dups] - warning("Some names are duplicated: ", paste(unique(duped_names), collapse=" "), call.=FALSE) - } - lst -} - - -# check if a string appears to be a http/https URL, optionally only https allowed -#' @rdname utils -#' @export is_url <- function(x, https_only=FALSE) { pat <- if(https_only) "^https://" else "^https?://" @@ -53,15 +23,6 @@ is_url <- function(x, https_only=FALSE) } -# TRUE for NULL and length-0 objects -#' @rdname utils -#' @export -is_empty <- function(x) -{ - length(x) == 0 -} - - # combine several pages of objects into a single list #' @rdname utils #' @export diff --git a/man/az_resource.Rd b/man/az_resource.Rd index b5464d4..55e8867 100644 --- a/man/az_resource.Rd +++ b/man/az_resource.Rd @@ -19,6 +19,9 @@ Class representing a generic Azure resource. \item \code{sync_fields()}: Synchronise the R object with the resource it represents in Azure. Returns the \code{properties$provisioningState} field, so you can query this programmatically to check if a resource has finished provisioning. Not all resource types require explicit provisioning, in which case this method will return NULL. \item \code{set_api_version(api_version, stable_only=TRUE)}: Set the API version to use when interacting with the host. If \code{api_version} is not supplied, use the latest version available, either the latest stable version (if \code{stable_only=TRUE}) or the latest preview version (if \code{stable_only=FALSE}). \item \code{get_api_version()}: Get the current API version. +\item \code{get_subresource(type, name)}: Get a sub-resource of this resource. See 'Sub-resources' below. +\item \code{create_subresource(type, name, ...)}: Create a sub-resource of this resource. +\item \code{delete_subresource(type, name, confirm=TRUE)}: Delete a sub-resource of this resource. \item \code{do_operation(...)}: Carry out an operation. See 'Operations' for more details. \item \code{set_tags(..., keep_existing=TRUE)}: Set the tags on this resource. The tags can be either names or name-value pairs. To delete a tag, set it to \code{NULL}. \item \code{get_tags()}: Get the tags on this resource. @@ -75,6 +78,16 @@ The \code{do_operation()} method allows you to carry out arbitrary operations on Consult the Azure documentation for your resource to find out what operations are supported. } +\section{Sub-resources}{ + +Some resource types can have sub-resources: objects exposed by Resource Manager that make up a part of their parent's functionality. For example, a storage account (type \code{Microsoft.Storage/storageAccounts}) provides the blob storage service, which can be accessed via Resource Manager as a sub-resource of type \code{Microsoft.Storage/storageAccounts/blobServices/default}. + +To retrieve an existing sub-resource, use the \code{get_subresource()} method. You do not need to include the parent resource's type and name. For example, if \code{res} is a resource for a storage account, and you want to retrieve the sub-resource for the blob container "myblobs", call\preformatted{res$get_subresource(type="blobServices/default/containers", name="myblobs") +} + +Notice that the storage account's resource type and name are omitted from the \code{get_subresource} arguments. Similarly, to create a new subresource, call the \code{create_subresource()} method with the same naming convention, passing any required fields as named arguments; and to delete it, call \code{delete_subresource()}. +} + \section{Role-based access control}{ AzureRMR implements a subset of the full RBAC functionality within Azure Active Directory. You can retrieve role definitions and add and remove role assignments, at the subscription, resource group and resource levels. See \link{rbac} for more information. @@ -121,7 +134,12 @@ vm$do_operation(http_verb="PATCH", # sync with Azure: useful to track resource creation/update status vm$sync_fields() -# delete a resource +## subresource: create a public blob container +stor$create_subresource(type="blobservices/default/containers", name="mycontainer", + properties=list(publicAccess="container")) + +## delete a subresource and resource +stor$delete_subresource(type="blobservices/default/containers", name="mycontainer") stor$delete() } diff --git a/man/az_rm.Rd b/man/az_rm.Rd index 9192250..dea9e62 100644 --- a/man/az_rm.Rd +++ b/man/az_rm.Rd @@ -33,9 +33,11 @@ To authenticate with the \code{az_rm} class directly, provide the following argu \item \code{username}: if \code{auth_type == "resource_owner"}, your username. \item \code{certificate}: If `auth_type == "client_credentials", a certificate to authenticate with. This is a more secure alternative to using an app secret. \item \code{auth_type}: The OAuth authentication method to use, one of "client_credentials", "authorization_code", "device_code" or "resource_owner". See \link{get_azure_token} for how the default method is chosen, along with some caveats. +\item \code{version}: The Azure Active Directory version to use for authenticating. \item \code{host}: your ARM host. Defaults to \verb{https://management.azure.com/}. Change this if you are using a government or private cloud. \item \code{aad_host}: Azure Active Directory host for authentication. Defaults to \verb{https://login.microsoftonline.com/}. Change this if you are using a government or private cloud. \item \code{...}: Further arguments to pass to \code{get_azure_token}. +\item \code{scopes}: The Azure Service Management scopes (permissions) to obtain for this login. Only for \code{version=2}. \item \code{token}: Optionally, an OAuth 2.0 token, of class \link{AzureToken}. This allows you to reuse the authentication details for an existing session. If supplied, all other arguments will be ignored. } } diff --git a/man/azure_login.Rd b/man/azure_login.Rd index 3076af2..8024823 100644 --- a/man/azure_login.Rd +++ b/man/azure_login.Rd @@ -9,9 +9,10 @@ \usage{ create_azure_login(tenant = "common", app = .az_cli_app_id, password = NULL, username = NULL, certificate = NULL, - auth_type = NULL, host = "https://management.azure.com/", - aad_host = "https://login.microsoftonline.com/", config_file = NULL, - token = NULL, graph_host = "https://graph.microsoft.com/", ...) + auth_type = NULL, version = 2, host = "https://management.azure.com/", + aad_host = "https://login.microsoftonline.com/", scopes = ".default", + config_file = NULL, token = NULL, + graph_host = "https://graph.microsoft.com/", ...) get_azure_login(tenant = "common", selection = NULL, refresh = TRUE) @@ -32,10 +33,14 @@ list_azure_logins() \item{auth_type}{The OAuth authentication method to use, one of "client_credentials", "authorization_code", "device_code" or "resource_owner". If \code{NULL}, this is chosen based on the presence of the \code{username} and \code{password} arguments.} +\item{version}{The Azure Active Directory version to use for authenticating.} + \item{host}{Your ARM host. Defaults to \verb{https://management.azure.com/}. Change this if you are using a government or private cloud.} \item{aad_host}{Azure Active Directory host for authentication. Defaults to \verb{https://login.microsoftonline.com/}. Change this if you are using a government or private cloud.} +\item{scopes}{The Azure Service Management scopes (permissions) to obtain for this login. Only for \code{version=2}.} + \item{config_file}{Optionally, a JSON file containing any of the arguments listed above. Arguments supplied in this file take priority over those supplied on the command line. You can also use the output from the Azure CLI \verb{az ad sp create-for-rbac} command.} \item{token}{Optionally, an OAuth 2.0 token, of class \link{AzureToken}. This allows you to reuse the authentication details for an existing session. If supplied, the other arguments above to \code{create_azure_login} will be ignored.} diff --git a/man/format.Rd b/man/format.Rd deleted file mode 100644 index 6978d37..0000000 --- a/man/format.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/format.R -\name{format_public_fields} -\alias{format_public_fields} -\alias{format_public_methods} -\title{Format an Azure object} -\usage{ -format_public_fields(env, exclude = character(0)) - -format_public_methods(env) -} -\arguments{ -\item{env}{An R6 object's environment for printing.} - -\item{exclude}{Objects in \code{env} to exclude from the printout.} -} -\description{ -Miscellaneous functions for printing Azure R6 objects -} -\details{ -These functions are utilities to aid in printing Azure R6 objects. They are not meant to be called by the user. -} diff --git a/man/reexports.Rd b/man/reexports.Rd index 1164845..a3494b1 100644 --- a/man/reexports.Rd +++ b/man/reexports.Rd @@ -1,8 +1,12 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/reexport_AzureAuth.R +% Please edit documentation in R/AzureRMR.R, R/reexport_AzureAuth.R \docType{import} \name{reexports} \alias{reexports} +\alias{named_list} +\alias{is_empty} +\alias{format_public_fields} +\alias{format_public_methods} \alias{clean_token_directory} \alias{delete_azure_token} \alias{get_azure_token} @@ -19,5 +23,7 @@ below to see their documentation. \describe{ \item{AzureAuth}{\code{\link[AzureAuth]{AzureR_dir}}, \code{\link[AzureAuth:get_azure_token]{clean_token_directory}}, \code{\link[AzureAuth:get_azure_token]{delete_azure_token}}, \code{\link[AzureAuth]{get_azure_token}}, \code{\link[AzureAuth:get_azure_token]{is_azure_token}}, \code{\link[AzureAuth:get_azure_token]{is_azure_v1_token}}, \code{\link[AzureAuth:get_azure_token]{is_azure_v1_token}}, \code{\link[AzureAuth:guid]{is_guid}}, \code{\link[AzureAuth:get_azure_token]{list_azure_tokens}}} + + \item{AzureGraph}{\code{\link[AzureGraph:format]{format_public_fields}}, \code{\link[AzureGraph:format]{format_public_methods}}, \code{\link[AzureGraph:utils]{is_empty}}, \code{\link[AzureGraph:utils]{named_list}}} }} diff --git a/man/utils.Rd b/man/utils.Rd index 08e2fd4..0f66460 100644 --- a/man/utils.Rd +++ b/man/utils.Rd @@ -1,45 +1,33 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/utils.R -\name{named_list} -\alias{named_list} +\name{is_url} \alias{is_url} -\alias{is_empty} \alias{get_paged_list} \title{Miscellaneous utility functions} \usage{ -named_list(lst = NULL, name_fields = "name") - is_url(x, https_only = FALSE) -is_empty(x) - get_paged_list(lst, token, next_link_name = "nextLink", value_name = "value") } \arguments{ -\item{lst}{A named list of objects.} - -\item{name_fields}{The components of the objects in \code{lst}, to be used as names.} - -\item{x}{For \code{is_url} and \code{is_empty}, An R object.} +\item{x}{For \code{is_url}, An R object.} \item{https_only}{For \code{is_url}, whether to allow only HTTPS URLs.} +\item{lst}{A named list of objects.} + \item{token}{For \code{get_paged_list}, an Azure OAuth token, of class \link{AzureToken}.} \item{next_link_name, value_name}{For \code{get_paged_list}, the names of the next link and value components in the \code{lst} argument. The default values are correct for Resource Manager.} } \value{ -For \code{named_list}, the list that was passed in but with names. An empty input results in a \emph{named list} output: a list of length 0, with a \code{names} attribute. - For \code{get_paged_list}, a list. -For \code{is_url}, whether the object appears to be a URL (is character of length 1, and starts with the string \code{"http"}). Optionally, restricts the check to HTTPS URLs only. For \code{is_empty}, whether the length of the object is zero (this includes the special case of \code{NULL}). +For \code{is_url}, whether the object appears to be a URL (is character of length 1, and starts with the string \code{"http"}). Optionally, restricts the check to HTTPS URLs only. } \description{ Miscellaneous utility functions } \details{ -\code{named_list} extracts from each object in \code{lst}, the components named by \code{name_fields}. It then constructs names for \code{lst} from these components, separated by a \code{"/"}. - \code{get_paged_list} reconstructs a complete list of objects from a paged response. Many Resource Manager list operations will return \emph{paged} output, that is, the response contains a subset of all items, along with a URL to query to retrieve the next subset. \code{get_paged_list} retrieves each subset and returns all items in a single list. } diff --git a/tests/testthat/test01_auth.R b/tests/testthat/test01_auth.R index e74b9a4..95a4aea 100644 --- a/tests/testthat/test01_auth.R +++ b/tests/testthat/test01_auth.R @@ -9,15 +9,18 @@ if(tenant == "" || app == "" || password == "" || subscription == "") skip("Authentication tests skipped: ARM credentials not set") +clean_token_directory(confirm=FALSE) suppressWarnings(file.remove(file.path(AzureR_dir(), "arm_logins.json"))) +scopes <- c("https://management.azure.com/.default", "openid", "offline_access") + test_that("ARM authentication works", { az <- az_rm$new(tenant=tenant, app=app, password=password) expect_is(az, "az_rm") expect_true(is_azure_token(az$token)) - tok <- get_azure_token("https://management.azure.com/", tenant, app, password) + tok <- get_azure_token(scopes, tenant, app, password, version=2) az2 <- az_rm$new(token=tok) expect_is(az2, "az_rm") }) @@ -40,7 +43,7 @@ test_that("Login interface works", az5 <- get_azure_login(tenant) expect_is(az5, "az_rm") - tok <- get_azure_token("https://management.azure.com/", tenant, app, password) + tok <- get_azure_token(scopes, tenant, app, password, version=2) az6 <- create_azure_login(token=tok, graph_host=NULL) expect_is(az6, "az_rm") }) @@ -58,6 +61,10 @@ test_that("Graph interop works", gr <- AzureGraph::get_graph_login(tenant) expect_is(gr, "ms_graph") + expect_true( + (!is.null(gr$token$resource) && grepl("graph\\.microsoft\\.com", gr$token$resource)) || + (!is.null(gr$token$scope) && any(grepl("graph\\.microsoft\\.com", gr$token$scope))) + ) }) test_that("Top-level do_operation works", diff --git a/tests/testthat/test02_sub.R b/tests/testthat/test02_sub.R index 1452f44..cafcba6 100644 --- a/tests/testthat/test02_sub.R +++ b/tests/testthat/test02_sub.R @@ -67,10 +67,10 @@ test_that("List filters work", function(r) is_resource(r) && r$type == "Microsoft.Storage/storageAccounts" && !is_empty(r$ext$createdTime)))) }) -test_that("Subscription methods work with AAD v2.0", +test_that("Subscription methods work with AAD v1.0", { - token <- get_azure_token(c("https://management.azure.com/.default", "offline_access"), - tenant=tenant, app=app, password=password, version=2) + token <- get_azure_token("https://management.azure.com/", + tenant=tenant, app=app, password=password, version=1) az <- az_rm$new(token=token) subs <- az$list_subscriptions() diff --git a/tests/testthat/test04a_subresource.R b/tests/testthat/test04a_subresource.R new file mode 100644 index 0000000..8edcd67 --- /dev/null +++ b/tests/testthat/test04a_subresource.R @@ -0,0 +1,39 @@ +context("Sub-resources") + +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("Resource group method tests skipped: ARM credentials not set") + +rgname <- paste(sample(letters, 20, replace=TRUE), collapse="") +rg <- az_rm$ + new(tenant=tenant, app=app, password=password)$ + get_subscription(subscription)$ + create_resource_group(rgname, location="australiaeast") + + +test_that("Resource methods work", +{ + resname <- paste0(sample(letters, 20), collapse="") + # resource with sub-resources + res <- rg$create_resource(type="Microsoft.Storage/storageAccounts", name=resname, + kind="StorageV2", + sku=list(name="Standard_LRS"), + wait=TRUE) + expect_true(is_resource(res)) + + subresname <- paste0(sample(letters, 20), collapse="") + subres <- res$create_subresource(type="blobservices/default/containers", name=subresname) + expect_true(is_resource(subres)) + + expect_true(is_resource(res$get_subresource(type="blobservices/default/containers", name=subresname))) + + expect_message(res$delete_subresource(type="blobservices/default/containers", name=subresname, confirm=FALSE)) +}) + +rg$delete(confirm=FALSE) + +