* initial commit

* acr initial login working

* docker api works

* make docker_registry a function

* make kubernetes_cluster a function

* doc refresh

* fixes

* aci working

* aks working

* 1.0.3 note

* add print method, check for nulls
This commit is contained in:
Hong Ooi 2019-07-03 03:44:55 +10:00 коммит произвёл GitHub
Родитель 099d9b9511
Коммит e21e31dc92
21 изменённых файлов: 662 добавлений и 264 удалений

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

@ -8,3 +8,4 @@ CONTRIBUTING.md
drat.sh
.travis.yml
^LICENSE\.md$
azure-pipelines.yml

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

@ -1,5 +1,7 @@
# Generated by roxygen2: do not edit by hand
export(DockerRegistry)
export(KubernetesCluster)
export(aci)
export(aci_creds)
export(aci_ports)

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

@ -1,7 +1,15 @@
# AzureContainers 1.1.0
* Make `docker_registry` and `kubernetes_cluster` into constructor functions rather than R6 classes, for consistency with other AzureR packages. The corresponding class objects are now `DockerRegistry` and `KubernetesCluster`.
* Enable AAD authentication for ACR. By default, instantiating a new docker registry object will authenticate using the AAD credentials of the currently signed-in user. Alternative authentication details can be supplied to `docker_registry`, which will be passed to `AzureAuth::get_azure_token`. See the help for `docker_registry` for more information.
* Enable authenticating with service principals to ACR from ACI and AKS.
* By default, create new container instances with a managed service identity.
# AzureContainers 1.0.3
* Add `aks$update_service_password()` method to reset/update the service principal credentials.
* Send the docker password via `stdin`, rather than on the commandline.
* Not released to CRAN (superseded by 1.1.0 above).
# AzureContainers 1.0.2

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

@ -4,6 +4,8 @@ NULL
.AzureContainers <- new.env()
.az_cli_app_id <- "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
globalVariables("self", "AzureContainers")
.onLoad <- function(libname, pkgname)

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

@ -108,9 +108,9 @@ extract_creds.az_container_registry <- function(obj, ...)
extract_creds(obj$get_docker_registry())
}
extract_creds.docker_registry <- function(obj, ...)
extract_creds.DockerRegistry <- function(obj, ...)
{
list(server=obj$server, username=obj$username, password=obj$password)
list(server=obj$server$hostname, username=obj$username, password=obj$password)
}
extract_creds.aci_creds <- function(obj, ...)

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

@ -43,8 +43,14 @@
#' myacr$list_credentials()
#' myacr$list_policies()
#'
#' # get the registry endpoint
#' dockerreg <- myacr$get_docker_registry()
#' # see who has push and pull access
#' myacr$list_role_assignments()
#'
#' # get the registry endpoint (for interactive use)
#' myacr$get_docker_registry()
#'
#' # get the registry endpoint (admin user account)
#' myacr$get_docker_registry(as_admin=TRUE)
#'
#' }
#' @aliases az_container_registry
@ -72,16 +78,17 @@ public=list(
do.call(rbind, lapply(use, as.data.frame))
},
get_docker_registry=function(username=NULL, password=NULL)
get_docker_registry=function(..., as_admin=FALSE, token=self$token)
{
if(self$properties$adminUserEnabled)
server <- paste0("https://", self$properties$loginServer)
if(as_admin)
{
if(!self$properties$adminUserEnabled)
stop("Admin user account is disabled", call.=FALSE)
creds <- self$list_credentials()
if(is.null(username))
username <- creds$username
if(is.null(password))
password <- creds$passwords[1]
docker_registry(server, username=creds$username, password=creds$passwords[1], app=NULL)
}
docker_registry$new(self$properties$loginServer, username=username, password=password)
else docker_registry(server, ..., token=token)
}
))

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

@ -7,7 +7,7 @@
#' The following methods are available, in addition to those provided by the [AzureRMR::az_resource] class:
#' - `new(...)`: Initialize a new AKS object.
#' - `get_cluster(config, role)`: Return an object representing the Docker registry endpoint.
#' - `update_service_password(new_password=NULL, key="key1", duration=1, ...)`: Update the password for the service principal used to manage the cluster resources. The duration of the new password is 1 year by default. You can supply other authentication arguments to Microsoft Graph as part of the method call; see [AzureGraph::create_graph_login] and the examples below.
#' - `update_service_password(new_password=NULL, key="key1", duration=1, ...)`: Update the password for the service principal used to manage the cluster resources, returning the new password invisibly. The duration of the new password is 1 year by default. You can supply other authentication arguments to Microsoft Graph as part of the method call; see [AzureGraph::create_graph_login] and the examples below.
#'
#' @section Details:
#' Initializing a new object of this class can either retrieve an existing AKS resource, or create a new resource on the host. Generally, the best way to initialize an object is via the `get_aks`, `create_aks` or `list_aks` methods of the [az_resource_group] class, which handle the details automatically.
@ -90,7 +90,7 @@ public=list(
}
writeLines(profile, config)
kubernetes_cluster$new(config=config)
kubernetes_cluster(config=config)
},
update_service_password=function(new_password=NULL, key="key1", duration=1, ...)
@ -107,6 +107,7 @@ public=list(
)
self$do_operation(body=list(properties=props), encode="json", http_verb="PATCH")
self$sync_fields()
invisible(secret)
}
))

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

@ -1,29 +1,31 @@
#' Docker registry class
#'
#' Class representing a [Docker registry](https://docs.docker.com/registry/). Note that this class can be used to interface with any Docker registry that supports the HTTP V2 API, not just those created via the Azure Container Registry service.
#' Class representing a [Docker registry](https://docs.docker.com/registry/). Note that this class can be used to interface with any Docker registry that supports the HTTP V2 API, not just those created via the Azure Container Registry service. Use the [docker_registry] function to instantiate new objects of this class.
#'
#' @docType class
#' @section Methods:
#' The following methods are available, in addition to those provided by the [AzureRMR::az_resource] class:
#' - `new(...)`: Initialize a new registry object. See 'Details'.
#' - `login`: Login to the registry via `docker login`.
#' - `login(...)`: Do a local login to the registry via `docker login`; necessary if you want to push and pull images. By default, instantiating a new object of this class will also log you in. See 'Details' below.
#' - `push(src_image, dest_image)`: Push an image to the registry, using `docker tag` and `docker push`.
#' - `pull(image)`: Pulls an image from the registry, using `docker pull`.
#' - `delete_layer(layer, digest, confirm=TRUE)`: Deletes a layer from the registry.
#' - `pull(image)`: Pull an image from the registry, using `docker pull`.
#' - `get_image_manifest(image, tag="latest")`: Gets the manifest for an image.
#' - `get_image_digest(image, tag="latest")`: Gets the digest (SHA hash) for an image.
#' - `delete_image(image, digest, confirm=TRUE)`: Deletes an image from the registry.
#' - `list_repositories`: Lists the repositories (images) in the registry.
#' - `list_repositories()`: Lists the repositories (images) in the registry.
#'
#' @section Details:
#' The arguments to the `new()` method are:
#' - `server`: The name of the registry server.
#' The arguments to the `login()` method are:
#' - `tenant`: The Azure Active Directory (AAD) tenant for the registry.
#' - `username`: The username that Docker will use to authenticate with the registry. This can be either the admin username, if the registry was created with an admin account, or the ID of a registered app that has access to the registry.
#' - `password`: The password that Docker will use to authenticate with the registry.
#' - `login`: Whether to login to the registry immediately; defaults to TRUE.
#' - `app`: The app ID to use to authenticate with the registry. Set this to NULL to authenticate with a username and password, rather than via AAD.
#' - `...`: Further arguments passed to [AzureAuth::get_azure_token].
#' - `token`: An Azure token object. If supplied, all authentication details will be inferred from this.
#'
#' The `login()`, `push()` and `pull()` methods for this class call the `docker` commandline tool under the hood. This allows all the features supported by Docker to be available immediately, with a minimum of effort. Any calls to the `docker` tool will also contain the full commandline as the `cmdline` attribute of the (invisible) returned value; this allows scripts to be developed that can be run outside R.
#'
#' @seealso
#' [acr], [call_docker]
#' [acr], [docker_registry], [call_docker]
#'
#' [Docker commandline reference](https://docs.docker.com/engine/reference/commandline/cli/)
#'
@ -32,93 +34,127 @@
#' @examples
#' \dontrun{
#'
#' rg <- AzureRMR::get_azure_login()$
#' get_subscription("subscription_id")$
#' get_resource_group("rgname")
#' reg <- docker_registry("myregistry")
#'
#' # get the registry endpoint
#' dockerreg <- rg$get_acr("myregistry")$get_docker_registry()
#'
#' dockerreg$login()
#' dockerreg$list_repositories()
#' reg$list_repositories()
#'
#' # create an image from a Dockerfile in the current directory
#' call_docker("build -t myimage .")
#'
#' # push the image
#' dockerreg$push("myimage")
#' reg$push("myimage")
#'
#' reg$get_image_manifest("myimage")
#' reg$get_image_digest("myimage")
#'
#' }
#' @export
docker_registry <- R6::R6Class("docker_registry",
DockerRegistry <- R6::R6Class("DockerRegistry",
public=list(
server=NULL,
username=NULL,
password=NULL,
aad_token=NULL,
initialize=function(server, username=NULL, password=NULL, login=!is.null(username))
initialize=function(server, ..., login=TRUE)
{
self$server <- server
if(login)
self$login(username, password)
else invisible(NULL)
self$server <- httr::parse_url(server)
if(login) self$login(...)
},
login=function(username=NULL, password=NULL)
login=function(tenant="common", username=NULL, password=NULL, app=.az_cli_app_id, ..., token=NULL)
{
if(!is.null(username))
identity <- username
else stop("No login identity supplied", call.=FALSE)
# ways to login:
# via creds inferred from AAD token
# if app == NULL, with admin account
if(is.null(app))
{
if(is.null(username) || is.null(password))
stop("Must supply username and password for admin login", call.=FALSE)
self$username <- username
self$password <- password
self$username <- username
self$password <- password
}
else
{
# default is to reuse token from any existing AzureRMR login
if(is.null(token))
token <- AzureAuth::get_azure_token("https://management.azure.com/",
tenant=tenant, app=app, password=password, username=username, ...)
cmd <- paste("login --password-stdin --username", identity, self$server)
self$aad_token <- token
username <- "00000000-0000-0000-0000-000000000000"
password <- private$get_creds_from_aad()
}
# special-casing Docker Hub
cmd <- if(self$server$hostname == "hub.docker.com")
paste("login --password-stdin --username", username)
else paste("login --password-stdin --username", username, self$server$hostname)
call_docker(cmd, input=password)
invisible(NULL)
},
push=function(src_image, dest_image)
{
if(missing(dest_image))
out1 <- if(missing(dest_image))
{
dest_image <- private$add_server(src_image)
out1 <- call_docker(sprintf("tag %s %s", src_image, dest_image))
dest_image <- private$paste_server(src_image)
call_docker(sprintf("tag %s %s", src_image, dest_image))
}
else
{
dest_image <- private$add_server(dest_image)
out1 <- call_docker(sprintf("tag %s %s", src_image, dest_image))
dest_image <- private$paste_server(dest_image)
call_docker(sprintf("tag %s %s", src_image, dest_image))
}
out2 <- call_docker(sprintf("push %s", dest_image))
out2 <- call_docker(sprintf("push %s", dest_image))
invisible(list(out1, out2))
},
pull=function(image)
{
image <- private$add_server(image)
image <- private$paste_server(image)
call_docker(sprintf("pull %s", image))
},
delete_layer=function(layer, digest, confirm=TRUE)
get_image_manifest=function(image, tag="latest")
{
if(confirm && interactive())
if(grepl(":", image))
{
yn <- readline(paste0("Do you really want to delete the layer '", image, "'? (y/N) "))
if(tolower(substr(yn, 1, 1)) != "y")
return(invisible(NULL))
tag <- sub("^[^:]+:", "", image)
image <- sub(":.+$", "", image)
}
res <- call_registry(self$server, self$username, self$password, file.path(layer, "blobs", digest),
http_verb="DELETE")
invisible(res)
op <- file.path(image, "manifests", tag)
perms <- paste("repository", image, "pull", sep=":")
cont <- self$call_registry(op, permissions=perms)
# registry API doesn't set content-type correctly, need to process further
jsonlite::fromJSON(rawToChar(cont), simplifyVector=FALSE)
},
delete_image=function(image, digest, confirm=TRUE)
get_image_digest=function(image, tag="latest")
{
if(grepl(":", image))
{
tag <- sub("^[^:]+:", "", image)
image <- sub(":.+$", "", image)
}
op <- file.path(image, "manifests", tag)
perms <- paste("repository", image, "pull", sep=":")
cont <- self$call_registry(op, http_verb="HEAD", permissions=perms, http_status_handler="pass")
httr::stop_for_status(cont)
httr::headers(cont)$`docker-content-digest`
},
delete_image=function(image, confirm=TRUE)
{
if(confirm && interactive())
{
@ -127,31 +163,194 @@ public=list(
return(invisible(NULL))
}
res <- call_registry(self$server, self$username, self$password, file.path(image, "manifests", digest),
http_verb="DELETE")
# get the digest for this image
digest <- self$get_image_digest(image)
if(is_empty(digest))
stop("Unable to find digest info for image", call.=FALSE)
op <- file.path(image, "manifests", digest)
perms <- paste("repository", image, "delete", sep=":")
res <- self$call_registry(op, http_verb="DELETE", permissions=perms)
invisible(res)
},
list_repositories=function()
{
res <- call_registry(self$server, self$username, self$password, "_catalog")
res <- self$call_registry("_catalog", permissions="registry:catalog:*")
unlist(res$repositories)
},
call_registry=function(op, ..., encode="form",
http_verb=c("GET", "DELETE", "PUT", "POST", "HEAD", "PATCH"),
http_status_handler=c("stop", "warn", "message", "pass"),
permissions="")
{
auth <- if(is.null(self$aad_token))
{
userpass <- openssl::base64_encode(paste(self$username, self$password, sep=":"))
paste("Basic", userpass)
}
else
{
creds <- private$get_creds_from_aad()
access_token <- private$get_access_token(creds, permissions)
paste("Bearer", access_token)
}
headers <- httr::add_headers(
Accept="application/vnd.docker.distribution.manifest.v2+json",
Authorization=auth
)
uri <- self$server
uri$path <- paste0("/v2/", op)
res <- httr::VERB(match.arg(http_verb), uri, headers, ..., encode=encode)
process_registry_response(res, match.arg(http_status_handler))
},
print=function(...)
{
cat("Docker registry '", self$server$hostname, "'\n", sep="")
cat("<Authentication>\n")
if(is.null(self$aad_token))
{
cat(" Username:", self$username, "\n")
cat(" Password: <hidden>\n")
}
else cat(" Via Azure Active Directory\n")
cat("---\n")
cat(format_public_methods(self))
invisible(self)
}
),
private=list(
add_server=function(image)
paste_server=function(image)
{
server <- paste0(self$server, "/")
# special-casing Docker Hub
server <- if(self$server$hostname == "hub.docker.com")
self$username
else self$server$hostname
server <- paste0(server, "/")
has_server <- substr(image, 1, nchar(server)) == server
if(!has_server)
paste0(server, image)
else image
},
get_creds_from_aad=function()
{
if(!self$aad_token$validate())
self$aad_token$refresh()
uri <- self$server
uri$path <- "oauth2/exchange"
tenant <- if(self$aad_token$tenant == "common")
AzureAuth::decode_jwt(self$aad_token$credentials$access_token)$payload$tid
else self$aad_token$tenant
res <- httr::POST(uri,
body=list(
grant_type="access_token",
service=uri$hostname,
tenant=tenant,
access_token=self$aad_token$credentials$access_token
),
encode="form"
)
httr::stop_for_status(res)
httr::content(res)$refresh_token
},
get_access_token=function(creds, permissions)
{
uri <- self$server
uri$path <- "oauth2/token"
res <- httr::POST(uri,
body=list(
grant_type="refresh_token",
service=uri$hostname,
scope=permissions,
refresh_token=creds
),
encode="form"
)
httr::stop_for_status(res)
httr::content(res)$access_token
}
))
#' Create a new Docker registry object
#'
#' @param server The registry server. This can be a URL ("https://myregistry.azurecr.io") or a domain name label ("myregistry"); if the latter, the value of the `domain` argument is appended to obtain the full hostname.
#' @param tenant,username,password,app,... Authentication arguments to [AzureAuth::get_azure_token]. See 'Details' below.
#' @param domain The default domain for the registry server.
#' @param token An OAuth token, of class [AzureAuth::AzureToken]. If supplied, the authentication details for the registry will be inferred from this.
#' @param login Whether to perform a local login (requires that you have Docker installed). This is necessary if you want to push or pull images.
#'
#' @details
#' There are two ways to authenticate with an Azure Docker registry: via Azure Active Directory (AAD), or with a username and password. The latter is simpler, while the former is more complex but also more flexible and secure.
#'
#' The default method of authenticating is via AAD. Without any arguments, `docker_registry` will authenticate using the AAD credentials of the currently logged-in user. You can change this by supplying the appropriate arguments to `docker_registry`, which will be passed to `AzureAuth::get_azure_token`; alternatively, you can provide an existing token object.
#'
#' To authenticate via the admin user account, set `app=NULL` and supply the admin username and password in the corresponding arguments. Note that for this to work, the registry must have been created with the admin account enabled.
#'
#' Authenticating with a service principal can be done either indirectly via AAD, or via a username and password. See the examples below. The latter method is recommended, as it is both faster and allows easier interoperability with AKS and ACI.
#'
#' @return
#' An R6 object of class `DockerRegistry`.
#'
#' @seealso
#' [DockerRegistry] for methods available for interacting with the registry, [call_docker]
#'
#' [kubernetes_cluster] for the corresponding function to create a Kubernetes cluster object
#'
#' @examples
#' \dontrun{
#'
#' # connect to the Docker registry 'myregistry.azurecr.io', authenticating as the current user
#' docker_registry("myregistry")
#'
#' # same, but providing a full URL
#' docker_registry("https://myregistry.azurecr.io")
#'
#' # authenticating via the admin account
#' docker_registry("myregistry", username="admin", password="password", app=NULL)
#'
#' # authenticating with a service principal, method 1: recommended
#' docker_registry("myregistry", username="app_id", password="client_creds", app=NULL)
#'
#' # authenticating with a service principal, method 2
#' docker_registry("myregistry", app="app_id", password="client_creds")
#'
#' # authenticating from a managed service identity (MSI)
#' token <- AzureAuth::get_managed_token("https://management.azure.com/")
#' docker_registry("myregistry", token=token)
#'
#' # you can also interact with a registry outside Azure
#' # note that some registry methods, and AAD authentication, may not work in this case
#' docker_registry("https://hub.docker.com", username="mydockerid", password="password", app=NULL)
#'
#' }
#' @export
docker_registry <- function(server, tenant="common", username=NULL, password=NULL, app=.az_cli_app_id, ...,
domain="azurecr.io", token=NULL, login=TRUE)
{
if(!is_url(server))
server <- sprintf("https://%s.%s", server, domain)
DockerRegistry$new(server, tenant=tenant, username=username, password=password, app=app, ...,
token=token, login=login)
}
#' Call the docker commandline tool
#'
#' @param cmd The docker command line to execute.
@ -202,22 +401,6 @@ call_docker <- function(cmd="", ...)
}
call_registry <- function(server, username, password, ...,
http_verb=c("GET", "DELETE", "PUT", "POST", "HEAD", "PATCH"),
http_status_handler=c("stop", "warn", "message", "pass"))
{
auth_str <- openssl::base64_encode(paste(username, password, sep=":"))
url <- paste0("https://", server, "/v2/", ...)
headers <- httr::add_headers(Authorization=sprintf("Basic %s", auth_str))
http_status_handler <- match.arg(http_status_handler)
verb <- get(match.arg(http_verb), getNamespace("httr"))
res <- verb(url, headers)
process_registry_response(res, http_status_handler)
}
process_registry_response <- function(response, handler)
{
if(handler != "pass")
@ -233,4 +416,3 @@ process_registry_response <- function(response, handler)
else response
}

4
R/is.R
Просмотреть файл

@ -35,7 +35,7 @@ is_aci <- function(object)
#' @export
is_docker_registry <- function(object)
{
R6::is.R6(object) && inherits(object, "docker_registry")
R6::is.R6(object) && inherits(object, "DockerRegistry")
}
@ -43,5 +43,5 @@ is_docker_registry <- function(object)
#' @export
is_kubernetes_cluster <- function(object)
{
R6::is.R6(object) && inherits(object, "kubernetes_cluster")
R6::is.R6(object) && inherits(object, "KubernetesCluster")
}

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

@ -1,6 +1,6 @@
#' Kubernetes cluster class
#'
#' Class representing a [Kubernetes](https://kubernetes.io/docs/home/) cluster. Note that this class can be used to interface with any Docker registry that supports the HTTP V2 API, not just those created via the Azure Container Registry service.
#' Class representing a [Kubernetes](https://kubernetes.io/docs/home/) cluster. Note that this class can be used to interface with any Docker registry that supports the HTTP V2 API, not just those created via the Azure Container Registry service. Use the [kubernetes_cluster] function to instantiate new objects of this class.
#'
#' @docType class
#' @section Methods:
@ -19,11 +19,11 @@
#' - `helm(cmd)`: Run a `helm` command on this cluster.
#'
#' @section Initialization:
#' The `new()` method takes one argument: `config`, the name of the file containing the configuration details for the cluster. This should be a yaml or json file in the standard Kubernetes configuration format. Set this to NULL to use the default `~/.kube/config` file.
#' The `new()` method takes one argument: `config`, the name of the file containing the configuration details for the cluster. This should be a YAML or JSON file in the standard Kubernetes configuration format. Set this to NULL to use the default `~/.kube/config` file.
#'
#' @section Secrets:
#' To allow a cluster to authenticate with a Docker registry, call the `create_registry_secret` method with the following arguments:
#' - `registry`: An object of class either [acr] representing an Azure Container Registry service, or [docker_registry] representing the registry itself.
#' The recommended way to allow a cluster to authenticate with a Docker registry is to give its service principal the appropriate role-based access. However, you can also authenticate with a username and password. To do this, call the `create_registry_secret` method with the following arguments:
#' - `registry`: An object of class either [acr] representing an Azure Container Registry service, or [DockerRegistry] representing the registry itself.
#' - `secret_name`: The name to give the secret. Defaults to the name of the registry server.
#' - `email`: The email address for the Docker registry.
#'
@ -90,7 +90,7 @@
#'
#' }
#' @export
kubernetes_cluster <- R6::R6Class("kubernetes_cluster",
KubernetesCluster <- R6::R6Class("KubernetesCluster",
public=list(
@ -99,13 +99,19 @@ public=list(
private$config <- config
},
create_registry_secret=function(registry, secret_name=registry$server, email)
create_registry_secret=function(registry, secret_name=registry$server$hostname, email)
{
if(is_acr(registry))
registry <- registry$get_docker_registry(registry)
registry <- registry$get_docker_registry(as_admin=TRUE)
if(!is_docker_registry(registry))
stop("Must supply a Docker registry object", call.=FALSE)
if(is.null(registry$username) || is.null(registry$password))
stop("Docker registry object does not contain a username/password", call.=FALSE)
cmd <- paste0("create secret docker-registry ", secret_name,
" --docker-server=", registry$server,
" --docker-server=", registry$server$hostname,
" --docker-username=", registry$username,
" --docker-password=", registry$password,
" --docker-email=", email)
@ -214,6 +220,32 @@ private=list(
))
#' Create a new Kubernetes cluster object
#'
#' @param config The name of the file containing the configuration details for the cluster. This should be a YAML or JSON file in the standard Kubernetes configuration format. Set this to NULL to use the default `~/.kube/config` file.
#' @details
#' @return
#' An R6 object of class `KubernetesCluster`.
#'
#' @seealso
#' [KubernetesCluster] for methods for working with the cluster, [call_kubectl], [call_helm]
#'
#' [docker_registry] for the corresponding function to create a Docker registry object
#'
#' @examples
#' \dontrun{
#'
#' kubernetes_cluster()
#' kubernetes_cluster("myconfig.yaml")
#'
#' }
#' @export
kubernetes_cluster <- function(config=NULL)
{
KubernetesCluster$new(config)
}
#' Call the Kubernetes commandline tool, kubectl
#'
#' @param cmd The kubectl command line to execute.

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

@ -16,35 +16,38 @@ Note that AzureContainers can talk to any Docker registry that uses the [V2 HTTP
Here is a sample R workflow to package up an R model as a container, deploy it to a Kubernetes cluster, and expose it as a service.
```r
# login to Azure ---
az <- AzureRMR::az_rm$new("<tenant_id>", "<app_id>", "<secret>")
library(AzureContainers)
az <- AzureRMR::get_azure_login()
resgroup <- az$
get_subscription("<subscription_id>")$
create_resource_group("myresgroup", location="australiaeast")
# create container registry ---
# create container registry
acr <- resgroup$create_acr("myacr", location="australiaeast")
# create Docker image from a predefined Dockerfile ---
# create Docker image from a predefined Dockerfile
call_docker("build -t newcontainer .")
# get registry endpoint, upload image ---
# get registry endpoint, upload image
reg <- acr$get_docker_registry()
reg$push("newcontainer")
# create Kubernetes cluster with 2 nodes ---
# create Kubernetes cluster with 2 nodes
aks <- resgroup$create_aks("myakscluster",
location="australiaeast",
agent_pools=aks_pools("pool1", 2, "Standard_DS2_v2", "Linux"))
# give the cluster pull access to the registry
aks_app <- aks$properties$servicePrincipalProfile$clientId
acr$add_role_assignment(
principal=AzureGraph::get_graph_login()$get_app(aks_app),
role="Acrpull"
)
# get cluster endpoint, deploy from ACR to AKS with predefined yaml definition file ---
# get cluster endpoint, deploy from ACR to AKS with predefined yaml definition file
clus <- aks$get_cluster()
clus$create_registry_secret(reg, email="email@example.com")
clus$create("model1.yaml")
clus$get("service")
```

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

@ -0,0 +1,68 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/docker_registry.R
\docType{class}
\name{DockerRegistry}
\alias{DockerRegistry}
\title{Docker registry class}
\format{An object of class \code{R6ClassGenerator} of length 24.}
\usage{
DockerRegistry
}
\description{
Class representing a \href{https://docs.docker.com/registry/}{Docker registry}. Note that this class can be used to interface with any Docker registry that supports the HTTP V2 API, not just those created via the Azure Container Registry service. Use the \link{docker_registry} function to instantiate new objects of this class.
}
\section{Methods}{
The following methods are available, in addition to those provided by the \link[AzureRMR:az_resource]{AzureRMR::az_resource} class:
\itemize{
\item \code{login(...)}: Do a local login to the registry via \code{docker login}; necessary if you want to push and pull images. By default, instantiating a new object of this class will also log you in. See 'Details' below.
\item \code{push(src_image, dest_image)}: Push an image to the registry, using \code{docker tag} and \code{docker push}.
\item \code{pull(image)}: Pull an image from the registry, using \code{docker pull}.
\item \code{get_image_manifest(image, tag="latest")}: Gets the manifest for an image.
\item \code{get_image_digest(image, tag="latest")}: Gets the digest (SHA hash) for an image.
\item \code{delete_image(image, digest, confirm=TRUE)}: Deletes an image from the registry.
\item \code{list_repositories()}: Lists the repositories (images) in the registry.
}
}
\section{Details}{
The arguments to the \code{login()} method are:
\itemize{
\item \code{tenant}: The Azure Active Directory (AAD) tenant for the registry.
\item \code{username}: The username that Docker will use to authenticate with the registry. This can be either the admin username, if the registry was created with an admin account, or the ID of a registered app that has access to the registry.
\item \code{password}: The password that Docker will use to authenticate with the registry.
\item \code{app}: The app ID to use to authenticate with the registry. Set this to NULL to authenticate with a username and password, rather than via AAD.
\item \code{...}: Further arguments passed to \link[AzureAuth:get_azure_token]{AzureAuth::get_azure_token}.
\item \code{token}: An Azure token object. If supplied, all authentication details will be inferred from this.
}
The \code{login()}, \code{push()} and \code{pull()} methods for this class call the \code{docker} commandline tool under the hood. This allows all the features supported by Docker to be available immediately, with a minimum of effort. Any calls to the \code{docker} tool will also contain the full commandline as the \code{cmdline} attribute of the (invisible) returned value; this allows scripts to be developed that can be run outside R.
}
\examples{
\dontrun{
reg <- docker_registry("myregistry")
reg$list_repositories()
# create an image from a Dockerfile in the current directory
call_docker("build -t myimage .")
# push the image
reg$push("myimage")
reg$get_image_manifest("myimage")
reg$get_image_digest("myimage")
}
}
\seealso{
\link{acr}, \link{docker_registry}, \link{call_docker}
\href{https://docs.docker.com/engine/reference/commandline/cli/}{Docker commandline reference}
\href{https://docs.docker.com/registry/spec/api/}{Docker registry API}
}
\keyword{datasets}

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

@ -0,0 +1,113 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/kubernetes_cluster.R
\docType{class}
\name{KubernetesCluster}
\alias{KubernetesCluster}
\title{Kubernetes cluster class}
\format{An object of class \code{R6ClassGenerator} of length 24.}
\usage{
KubernetesCluster
}
\description{
Class representing a \href{https://kubernetes.io/docs/home/}{Kubernetes} cluster. Note that this class can be used to interface with any Docker registry that supports the HTTP V2 API, not just those created via the Azure Container Registry service. Use the \link{kubernetes_cluster} function to instantiate new objects of this class.
}
\section{Methods}{
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 registry object. See 'Initialization' below.
\item \code{create_registry_secret(registry, secret_name, email)}: Provide authentication secret for a Docker registry. See 'Secrets' below.
\item \code{delete_registry_secret(secret_name)}: Delete a registry authentication secret.
\item \code{create(file)}: Creates a deployment or service from a file, using \code{kubectl create -f}.
\item \code{get(type)}: Get information about resources, using \code{kubectl get}.
\item \code{run(name, image)}: Run an image using \code{kubectl run --image}.
\item \code{expose(name, type, file)}: Expose a service using \code{kubectl expose}. If the \code{file} argument is provided, read service information from there.
\item \code{delete(type, name, file)}: Delete a resource (deployment or service) using \code{kubectl delete}. If the \code{file} argument is provided, read resource information from there.
\item \code{apply(file)}: Apply a configuration file, using \code{kubectl apply -f}.
\item \code{show_dashboard(port)}: Display the cluster dashboard. By default, use local port 30000.
\item \code{kubectl(cmd)}: Run an arbitrary \code{kubectl} command on this cluster. Called by the other methods above.
\item \code{helm(cmd)}: Run a \code{helm} command on this cluster.
}
}
\section{Initialization}{
The \code{new()} method takes one argument: \code{config}, the name of the file containing the configuration details for the cluster. This should be a YAML or JSON file in the standard Kubernetes configuration format. Set this to NULL to use the default \code{~/.kube/config} file.
}
\section{Secrets}{
The recommended way to allow a cluster to authenticate with a Docker registry is to give its service principal the appropriate role-based access. However, you can also authenticate with a username and password. To do this, call the \code{create_registry_secret} method with the following arguments:
\itemize{
\item \code{registry}: An object of class either \link{acr} representing an Azure Container Registry service, or \link{DockerRegistry} representing the registry itself.
\item \code{secret_name}: The name to give the secret. Defaults to the name of the registry server.
\item \code{email}: The email address for the Docker registry.
}
}
\section{Kubectl}{
The methods for this class call the \code{kubectl} commandline tool, passing it the \code{--config} option to specify the configuration information for the cluster. This allows all the features supported by Kubernetes to be available immediately and with a minimum of effort, although it does require that \code{kubectl} be installed. Any calls to \code{kubectl} will also contain the full commandline as the \code{cmdline} attribute of the (invisible) returned value; this allows scripts to be developed that can be run outside R.
}
\examples{
\dontrun{
rg <- AzureRMR::get_azure_login()$
get_subscription("subscription_id")$
get_resource_group("rgname")
# get the cluster endpoint
kubclus <- rg$get_aks("mycluster")$get_cluster()
# get registry authentication secret
kubclus$create_registry_secret(rg$get_acr("myregistry"))
# deploy a service
kubclus$create("deployment.yaml")
# can also supply the deployment parameters inline
kubclus$create("
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: model1
spec:
replicas: 1
template:
metadata:
labels:
app: model1
spec:
containers:
- name: model1
image: myregistry.azurecr.io/model1
ports:
- containerPort: 8000
imagePullSecrets:
- name: myregistry.azurecr.io
---
apiVersion: v1
kind: Service
metadata:
name: model1-svc
spec:
selector:
app: model1
type: LoadBalancer
ports:
- protocol: TCP
port: 8000")
# track status
kubclus$get("deployment")
kubclus$get("service")
}
}
\seealso{
\link{aks}, \link{call_kubectl}
\href{https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands}{Kubectl commandline reference}
}
\keyword{datasets}

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

@ -51,8 +51,14 @@ myacr <- rg$get_acr("myregistry")
myacr$list_credentials()
myacr$list_policies()
# get the registry endpoint
dockerreg <- myacr$get_docker_registry()
# see who has push and pull access
myacr$list_role_assignments()
# get the registry endpoint (for interactive use)
myacr$get_docker_registry()
# get the registry endpoint (admin user account)
myacr$get_docker_registry(as_admin=TRUE)
}
}

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

@ -18,7 +18,7 @@ The following methods are available, in addition to those provided by the \link[
\itemize{
\item \code{new(...)}: Initialize a new AKS object.
\item \code{get_cluster(config, role)}: Return an object representing the Docker registry endpoint.
\item \code{update_service_password(new_password=NULL, key="key1", duration=1, ...)}: Update the password for the service principal used to manage the cluster resources. The duration of the new password is 1 year by default. You can supply other authentication arguments to Microsoft Graph as part of the method call; see \link[AzureGraph:create_graph_login]{AzureGraph::create_graph_login} and the examples below.
\item \code{update_service_password(new_password=NULL, key="key1", duration=1, ...)}: Update the password for the service principal used to manage the cluster resources, returning the new password invisibly. The duration of the new password is 1 year by default. You can supply other authentication arguments to Microsoft Graph as part of the method call; see \link[AzureGraph:create_graph_login]{AzureGraph::create_graph_login} and the examples below.
}
}

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

@ -1,69 +1,69 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/docker_registry.R
\docType{class}
\name{docker_registry}
\alias{docker_registry}
\title{Docker registry class}
\format{An object of class \code{R6ClassGenerator} of length 24.}
\title{Create a new Docker registry object}
\usage{
docker_registry
docker_registry(server, tenant = "common", username = NULL,
password = NULL, app = .az_cli_app_id, ..., domain = "azurecr.io",
token = NULL, login = TRUE)
}
\arguments{
\item{server}{The registry server. This can be a URL ("https://myregistry.azurecr.io") or a domain name label ("myregistry"); if the latter, the value of the \code{domain} argument is appended to obtain the full hostname.}
\item{tenant, username, password, app, ...}{Authentication arguments to \link[AzureAuth:get_azure_token]{AzureAuth::get_azure_token}. See 'Details' below.}
\item{domain}{The default domain for the registry server.}
\item{token}{An OAuth token, of class \link[AzureAuth:AzureToken]{AzureAuth::AzureToken}. If supplied, the authentication details for the registry will be inferred from this.}
\item{login}{Whether to perform a local login (requires that you have Docker installed). This is necessary if you want to push or pull images.}
}
\value{
An R6 object of class \code{DockerRegistry}.
}
\description{
Class representing a \href{https://docs.docker.com/registry/}{Docker registry}. Note that this class can be used to interface with any Docker registry that supports the HTTP V2 API, not just those created via the Azure Container Registry service.
Create a new Docker registry object
}
\section{Methods}{
\details{
There are two ways to authenticate with an Azure Docker registry: via Azure Active Directory (AAD), or with a username and password. The latter is simpler, while the former is more complex but also more flexible and secure.
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 registry object. See 'Details'.
\item \code{login}: Login to the registry via \code{docker login}.
\item \code{push(src_image, dest_image)}: Push an image to the registry, using \code{docker tag} and \code{docker push}.
\item \code{pull(image)}: Pulls an image from the registry, using \code{docker pull}.
\item \code{delete_layer(layer, digest, confirm=TRUE)}: Deletes a layer from the registry.
\item \code{delete_image(image, digest, confirm=TRUE)}: Deletes an image from the registry.
\item \code{list_repositories}: Lists the repositories (images) in the registry.
The default method of authenticating is via AAD. Without any arguments, \code{docker_registry} will authenticate using the AAD credentials of the currently logged-in user. You can change this by supplying the appropriate arguments to \code{docker_registry}, which will be passed to \code{AzureAuth::get_azure_token}; alternatively, you can provide an existing token object.
To authenticate via the admin user account, set \code{app=NULL} and supply the admin username and password in the corresponding arguments. Note that for this to work, the registry must have been created with the admin account enabled.
Authenticating with a service principal can be done either indirectly via AAD, or via a username and password. See the examples below. The latter method is recommended, as it is both faster and allows easier interoperability with AKS and ACI.
}
}
\section{Details}{
The arguments to the \code{new()} method are:
\itemize{
\item \code{server}: The name of the registry server.
\item \code{username}: The username that Docker will use to authenticate with the registry. This can be either the admin username, if the registry was created with an admin account, or the ID of a registered app that has access to the registry.
\item \code{password}: The password that Docker will use to authenticate with the registry.
\item \code{login}: Whether to login to the registry immediately; defaults to TRUE.
}
The \code{login()}, \code{push()} and \code{pull()} methods for this class call the \code{docker} commandline tool under the hood. This allows all the features supported by Docker to be available immediately, with a minimum of effort. Any calls to the \code{docker} tool will also contain the full commandline as the \code{cmdline} attribute of the (invisible) returned value; this allows scripts to be developed that can be run outside R.
}
\examples{
\dontrun{
rg <- AzureRMR::get_azure_login()$
get_subscription("subscription_id")$
get_resource_group("rgname")
# connect to the Docker registry 'myregistry.azurecr.io', authenticating as the current user
docker_registry("myregistry")
# get the registry endpoint
dockerreg <- rg$get_acr("myregistry")$get_docker_registry()
# same, but providing a full URL
docker_registry("https://myregistry.azurecr.io")
dockerreg$login()
dockerreg$list_repositories()
# authenticating via the admin account
docker_registry("myregistry", username="admin", password="password", app=NULL)
# create an image from a Dockerfile in the current directory
call_docker("build -t myimage .")
# authenticating with a service principal, method 1: recommended
docker_registry("myregistry", username="app_id", password="client_creds", app=NULL)
# push the image
dockerreg$push("myimage")
# authenticating with a service principal, method 2
docker_registry("myregistry", app="app_id", password="client_creds")
# authenticating from a managed service identity (MSI)
token <- AzureAuth::get_managed_token("https://management.azure.com/")
docker_registry("myregistry", token=token)
# you can also interact with a registry outside Azure
# note that some registry methods, and AAD authentication, may not work in this case
docker_registry("https://hub.docker.com", username="mydockerid", password="password", app=NULL)
}
}
\seealso{
\link{acr}, \link{call_docker}
\link{DockerRegistry} for methods available for interacting with the registry, \link{call_docker}
\href{https://docs.docker.com/engine/reference/commandline/cli/}{Docker commandline reference}
\href{https://docs.docker.com/registry/spec/api/}{Docker registry API}
\link{kubernetes_cluster} for the corresponding function to create a Kubernetes cluster object
}
\keyword{datasets}

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

@ -1,113 +1,30 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/kubernetes_cluster.R
\docType{class}
\name{kubernetes_cluster}
\alias{kubernetes_cluster}
\title{Kubernetes cluster class}
\format{An object of class \code{R6ClassGenerator} of length 24.}
\title{Create a new Kubernetes cluster object}
\usage{
kubernetes_cluster
kubernetes_cluster(config = NULL)
}
\arguments{
\item{config}{The name of the file containing the configuration details for the cluster. This should be a YAML or JSON file in the standard Kubernetes configuration format. Set this to NULL to use the default \code{~/.kube/config} file.}
}
\value{
An R6 object of class \code{KubernetesCluster}.
}
\description{
Class representing a \href{https://kubernetes.io/docs/home/}{Kubernetes} cluster. Note that this class can be used to interface with any Docker registry that supports the HTTP V2 API, not just those created via the Azure Container Registry service.
Create a new Kubernetes cluster object
}
\section{Methods}{
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 registry object. See 'Initialization' below.
\item \code{create_registry_secret(registry, secret_name, email)}: Provide authentication secret for a Docker registry. See 'Secrets' below.
\item \code{delete_registry_secret(secret_name)}: Delete a registry authentication secret.
\item \code{create(file)}: Creates a deployment or service from a file, using \code{kubectl create -f}.
\item \code{get(type)}: Get information about resources, using \code{kubectl get}.
\item \code{run(name, image)}: Run an image using \code{kubectl run --image}.
\item \code{expose(name, type, file)}: Expose a service using \code{kubectl expose}. If the \code{file} argument is provided, read service information from there.
\item \code{delete(type, name, file)}: Delete a resource (deployment or service) using \code{kubectl delete}. If the \code{file} argument is provided, read resource information from there.
\item \code{apply(file)}: Apply a configuration file, using \code{kubectl apply -f}.
\item \code{show_dashboard(port)}: Display the cluster dashboard. By default, use local port 30000.
\item \code{kubectl(cmd)}: Run an arbitrary \code{kubectl} command on this cluster. Called by the other methods above.
\item \code{helm(cmd)}: Run a \code{helm} command on this cluster.
}
}
\section{Initialization}{
The \code{new()} method takes one argument: \code{config}, the name of the file containing the configuration details for the cluster. This should be a yaml or json file in the standard Kubernetes configuration format. Set this to NULL to use the default \code{~/.kube/config} file.
}
\section{Secrets}{
To allow a cluster to authenticate with a Docker registry, call the \code{create_registry_secret} method with the following arguments:
\itemize{
\item \code{registry}: An object of class either \link{acr} representing an Azure Container Registry service, or \link{docker_registry} representing the registry itself.
\item \code{secret_name}: The name to give the secret. Defaults to the name of the registry server.
\item \code{email}: The email address for the Docker registry.
}
}
\section{Kubectl}{
The methods for this class call the \code{kubectl} commandline tool, passing it the \code{--config} option to specify the configuration information for the cluster. This allows all the features supported by Kubernetes to be available immediately and with a minimum of effort, although it does require that \code{kubectl} be installed. Any calls to \code{kubectl} will also contain the full commandline as the \code{cmdline} attribute of the (invisible) returned value; this allows scripts to be developed that can be run outside R.
}
\examples{
\dontrun{
rg <- AzureRMR::get_azure_login()$
get_subscription("subscription_id")$
get_resource_group("rgname")
# get the cluster endpoint
kubclus <- rg$get_aks("mycluster")$get_cluster()
# get registry authentication secret
kubclus$create_registry_secret(rg$get_acr("myregistry"))
# deploy a service
kubclus$create("deployment.yaml")
# can also supply the deployment parameters inline
kubclus$create("
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: model1
spec:
replicas: 1
template:
metadata:
labels:
app: model1
spec:
containers:
- name: model1
image: myregistry.azurecr.io/model1
ports:
- containerPort: 8000
imagePullSecrets:
- name: myregistry.azurecr.io
---
apiVersion: v1
kind: Service
metadata:
name: model1-svc
spec:
selector:
app: model1
type: LoadBalancer
ports:
- protocol: TCP
port: 8000")
# track status
kubclus$get("deployment")
kubclus$get("service")
kubernetes_cluster()
kubernetes_cluster("myconfig.yaml")
}
}
\seealso{
\link{aks}, \link{call_kubectl}
\link{KubernetesCluster} for methods for working with the cluster, \link{call_kubectl}, \link{call_helm}
\href{https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands}{Kubectl commandline reference}
\link{docker_registry} for the corresponding function to create a Docker registry object
}
\keyword{datasets}

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

@ -52,7 +52,7 @@ test_that("ACR works with app login",
role="owner"
)
reg <- acr$get_docker_registry(username=app, password=password)
reg <- acr$get_docker_registry(username=app, password=password, app=NULL)
expect_true(is_docker_registry(reg))
expect_identical(reg$username, app)

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

@ -34,20 +34,21 @@ test_that("ACI works",
acr <- rg$get_acr(acrname)
expect_true(is_acr(acr))
reg <- acr$get_docker_registry()
reg <- acr$get_docker_registry(as_admin=TRUE)
expect_true(is_docker_registry(reg))
expect_false(is.null(reg$username) || is.null(reg$password))
aciname2 <- paste0(sample(letters, 10, TRUE), collapse="")
aci2 <- rg$create_aci(aciname2,
image=paste0(reg$server, "/hello-world"),
image=paste0(reg$server$hostname, "/hello-world"),
registry_creds=reg)
expect_true(is_aci(aci2))
aciname3 <- paste0(sample(letters, 10, TRUE), collapse="")
aci3 <- rg$create_aci(aciname3,
image=paste0(reg$server, "/hello-world-sp"),
registry_creds=aci_creds(reg$server, app, password))
image=paste0(reg$server$hostname, "/hello-world-sp"),
registry_creds=aci_creds(reg$server$hostname, app, password))
expect_true(is_aci(aci3))
})

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

@ -12,24 +12,27 @@ acrname <- Sys.getenv("AZ_TEST_ACR")
if(acrname == "")
skip("AKS tests skipped: resource names not set")
rgname <- Sys.getenv("AZ_TEST_RG")
rg <- AzureRMR::az_rm$
new(tenant=tenant, app=app, password=password)$
get_subscription(subscription)$
get_resource_group(rgname)
acr <- rg$get_acr(acrname)
test_that("AKS works",
{
rgname <- Sys.getenv("AZ_TEST_RG")
rg <- AzureRMR::az_rm$
new(tenant=tenant, app=app, password=password)$
get_subscription(subscription)$
get_resource_group(rgname)
acr <- rg$get_acr(acrname)
expect_true(is_acr(acr))
reg <- acr$get_docker_registry()
reg <- acr$get_docker_registry(as_admin=TRUE)
expect_true(is_docker_registry(reg))
expect_false(is.null(reg$username) || is.null(reg$password))
expect_is(rg$list_kubernetes_versions(), "character")
aksname <- paste0(sample(letters, 10, TRUE), collapse="")
expect_true(is_aks(rg$create_aks(aksname, agent_pools=aks_pools("pool1", 3))))
expect_true(is_aks(rg$create_aks(aksname, agent_pools=aks_pools("pool1", 2))))
expect_true(is_aks(rg$list_aks()[[1]]))
aks <- rg$get_aks(aksname)
@ -44,3 +47,24 @@ test_that("AKS works",
clus$create_registry_secret(reg, email="me@example.com")
clus$create(hello_yaml)
})
test_that("AKS works with RBAC",
{
aksname <- paste0(sample(letters, 10, TRUE), collapse="")
aks <- rg$create_aks(aksname, agent_pools=aks_pools("pool1", 2))
expect_true(is_aks(aks))
aks_app <- aks$properties$servicePrincipalProfile$clientId
acr$add_role_assignment(
principal=AzureGraph::get_graph_login(tenant)$get_app(aks_app),
role="Acrpull"
)
clus <- aks$get_cluster()
expect_true(is_kubernetes_cluster(clus))
hello_yaml <- gsub("acrname", acrname, readLines("../resources/hello.yaml"))
clus$create(hello_yaml)
})

31
vignettes/vig03_rbac.Rmd Normal file
Просмотреть файл

@ -0,0 +1,31 @@
---
title: "Authenticating with role-based access controls"
Author: Hong Ooi
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{RBAC}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{utf8}
---
The easiest way to talk to an Azure Container Registry is via its admin account, and this is what is demonstrated in the other vignettes. However, this isn't necessarily the _best_ way. There is only one admin account, which has to be shared by all users, services and resources that want to access the registry. You can't selectively enable or disable access for an individual service; nor do you have fine-grained control over what actions they can take. To solve these problems, you can use role-based access controls (RBAC) to manage which services/resources have access to the registry.
The AzureR packages make it straightforward to use RBAC to authenticate with a registry.
```r
library(AzureContainers)
az <- AzureRMR::get_azure_login()
gr <- AzureGraph::get_graph_login()
rg <- az$
get_subscription("sub_id")$
get_resource_group("rgname")
acr <- rg$create_acr("myregistry")
app <- gr$create_app("myappname")
acr$add_role_assignment(app, "acrpush")
```