Acr rbac (#2)
* 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:
Родитель
099d9b9511
Коммит
e21e31dc92
|
@ -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)
|
||||
|
|
8
NEWS.md
8
NEWS.md
|
@ -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
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.
|
||||
|
|
25
README.md
25
README.md
|
@ -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")
|
||||
```
|
||||
|
|
|
@ -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}
|
|
@ -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}
|
10
man/acr.Rd
10
man/acr.Rd
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -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")
|
||||
```
|
||||
|
Загрузка…
Ссылка в новой задаче