R/droplet-actions.R

Defines functions droplet_ip droplet_neighbors neighbors droplet_actions droplet_upgrades_list droplet_kernels_list droplet_backups_list droplet_restore droplet_snapshots_list droplet_snapshot droplet_change_kernel droplet_rename droplet_rebuild droplet_resize droplet_action droplet_upgrade droplet_disable_backups droplet_enable_backups droplet_enable_private_networking droplet_enable_ipv6 droplet_reset_password droplet_power_on droplet_power_off droplet_shutdown droplet_power_cycle droplet_reboot droplet_delete droplet_wait capwords random_name droplet_create

Documented in droplet_action droplet_actions droplet_backups_list droplet_change_kernel droplet_create droplet_delete droplet_disable_backups droplet_enable_backups droplet_enable_ipv6 droplet_enable_private_networking droplet_ip droplet_kernels_list droplet_neighbors droplet_power_cycle droplet_power_off droplet_power_on droplet_reboot droplet_rebuild droplet_rename droplet_reset_password droplet_resize droplet_restore droplet_shutdown droplet_snapshot droplet_snapshots_list droplet_upgrade droplet_upgrades_list droplet_wait neighbors

#' Create a new droplet.
#'
#' There are defaults for each of size, image, and region so that a quick
#' one-liner with one parameter is possible: simply specify the name of the
#' droplet and your'e up and running.
#'
#' @export
#' @param name (character) Name of the droplet. The human-readable string you
#'   wish to use when displaying the Droplet name. The name, if set to a domain
#'   name managed in the DigitalOcean DNS management system, will configure a
#'   PTR record for the Droplet. The name set during creation will also
#'   determine the hostname for the Droplet in its internal configuration.
#'   Default: picks a random name from \code{\link{words}} if none supplied.
#' @param size (character) Size slug identifier. See \code{\link{sizes}()} for
#'   a complete list. Default: s-1vcpu-1gb, the smallest
#' @param image (character/numeric) The image ID of a public or private image,
#'   or the unique slug identifier for a public image. This image will be the
#'   base image for your droplet. See \code{\link{images}()} for a complete
#'   list. Use rstudio-20-04 for a DigitalOcean Marketplace image with R and
#'   Tidyverse readily available. Default: ubuntu-22-04-x64
#' @param region (character) The unique slug identifier for the region that you
#'   wish to deploy in. See \code{\link{regions}()} for a complete list.
#'   Default: sfo3
#' @param ssh_keys (character) A character vector of key names, an integer
#'   vector of key ids, or NULL, to use all keys in your account. Accounts
#'   with the corresponding private key will be able to log in to the droplet.
#'   See \code{\link{keys}()} for a list of the keys that you've added.
#'   Default: NULL
#' @param private_networking (logical) Use private networking. Private
#'   networking is currently only available in certain regions.
#'   Default: \code{FALSE}
#' @param tags (character) A vector of tag names to apply to the Droplet after
#'   it is created. Tag names can either be existing or new tags.
#'   Default: list()
#' @param backups (logical) Enable backups. A boolean indicating whether
#'   automated backups should be enabled for the droplet. Automated backups can
#'   only be enabled when the droplet is created, and cost extra.
#'   Default: FALSE
#' @param ipv6 (logical) A boolean indicating whether IPv6 is enabled on the
#'   droplet.
#' @param user_data (character) Gets passed to the droplet at boot time. Not
#'   all regions have this enabled, and is not used by all images.
#' @param cloud_config (character) Specify the name of a cloud config template
#'   to automatically generate \code{\link{cloud_config}} and submit in
#'   user metadata. Setting this is best practice: the built-in templates
#'   use security best practices (disabling root log-in, security autoupdates)
#'   to make it harder to hack your droplet.
#' @param wait If \code{TRUE} (default), wait until droplet has been
#' initialised and is ready for use. If set to \code{FALSE} we return a
#' droplet object right away after droplet creation request has been sent.
#' Note that there won't be an IP address in the object yet. Note that
#' waiting means we ping the DigitalOcean API to check on the status of your
#' droplet, which uses up your API requests. The option \code{do.wait_time}
#' can be set to any positive integer to determine how many seconds between
#' pings. The default is 1 sec. Note that if you are creating droplets in a
#' loop, parallel or otherwise, set \code{do.wait_time} within the loop
#' instead of outside of it.
#'
#' @param ... Additional options passed down to \code{\link[httr]{POST}}
#'
#' @details Note that if you exit the R session or kill the function call
#' after it's in waiting process (the string of ...), the droplet creation
#' will continue.
#' @template dropid
#'
#' @return A droplet object
#'
#' @examples \dontrun{
#' # by default we give your droplet a name
#' droplet_create()
#'
#' # you can set your own droplet name
#' droplet_create('droppinit')
#'
#' # set name, size, image, and region
#' droplet_create(name="newdrop", size = '512mb', image = 'ubuntu-22-04-x64',
#'   region = 'sfo3')
#'
#' # use an ssh key
#' droplet_create(ssh_keys=89103)
#'
#' # add tags
#' (d <- droplet_create(tags = c('venus', 'mars')))
#' summary(d)
#' }
droplet_create <- function(name = random_name(),
                        size = getOption("do_size", "s-1vcpu-1gb"),
                        image = getOption("do_image", "ubuntu-22-04-x64"),
                        region = getOption("do_region", "sfo3"),
                        ssh_keys = getOption("do_ssh_keys", NULL),
                        backups = getOption("do_backups", NULL),
                        ipv6 = getOption("do_ipv6", NULL),
                        private_networking =
                          getOption("do_private_networking", NULL),
                        tags = list(),
                        user_data = NULL,
                        cloud_config = NULL,
                        wait = TRUE,
                        ...) {

  ssh_keys <- standardise_keys(ssh_keys)
  if (length(ssh_keys) == 0) {
    warning("You have not specified any ssh_keys. This is NOT recommended.",
      " (You will receive an email with the root password in a few minutes)",
      call. = FALSE)
  }

  # Generate user_data if cloud_config specified
  if (!is.null(cloud_config)) {
    if (!is.null(user_data)) {
      stop("You may only specify one of cloud_config and user_data.",
        call. = FALSE)
    }

    user_data <- cloud_config(cloud_config, ssh_keys)
  }

  res <- do_POST('droplets',
                 body = list(
                   name = unbox(name),
                   size = unbox(size),
                   image = unbox(image),
                   region = unbox(region),
                   ssh_keys = I(ssh_keys),
                   backups = unbox(backups),
                   ipv6 = unbox(ipv6),
                   private_networking = unbox(private_networking),
                   tags = I(tags),
                   user_data = unbox(user_data)
                 ), ...
  )
  dres <- droplet(res$droplet$id)
  message("NB: This costs $", dres$size$price_hourly, " / hour ",
          "until you droplet_delete() it")

  if (wait) {
    droplet_wait(dres)
    droplet(dres$id)
  } else {
    droplet(dres$id)
  }
}

# random_name <- function() sample(words, size = 1)
random_name <- function(){
  sample_upcase <- function(x) capwords(sample(x, size = 1))
  paste0(sample_upcase(adjectives), sample_upcase(nouns))
}

capwords <- function(s, strict = FALSE, onlyfirst = FALSE) {
  cap <- function(s) paste(toupper(substring(s, 1, 1)), {
    s <- substring(s,2); if (strict) tolower(s) else s}, sep = "",
    collapse = " " )
  if (!onlyfirst) {
    sapply(strsplit(s, split = " "), cap, USE.NAMES = !is.null(names(s)))
  } else {
    sapply(s, function(x)
      paste(toupper(substring(x,1,1)),
            tolower(substring(x,2)),
            sep = "", collapse = " "), USE.NAMES = FALSE)
  }
}

#' Wait for a droplet to be ready.
#'
#' @param droplet  A droplet, or something that can be coerced to a droplet by
#'   \code{\link{as.droplet}}.
#' @export
#' @examples
#' \dontrun{
#' droplet_create() %>% droplet_wait()
#' }
droplet_wait <- function(droplet) {
  droplet <- as.droplet(droplet)

  action <- droplet_actions(droplet)[[1]]
  action_wait(action)
}


#' Delete a droplet.
#'
#' This method deletes one of your droplets - this is irreversible.
#'
#' @export
#' @param droplet A droplet, or something that can be coerced to a droplet by
#'   \code{\link{as.droplet}}.
#' @param tag (character) Name of a tag. optional
#' @param ... Additional options passed down to low-level API method.
#' @examples
#' \dontrun{
#' drops <- droplets()
#' drops[[1]] %>% droplet_delete()
#' drops[[2]] %>% droplet_delete()
#' droplet_create() %>% droplet_delete()
#'
#' droplet_delete("lombard")
#' droplet_delete(12345)
#'
#' # Delete all droplets
#' lapply(droplets(), droplet_delete)
#'
#' # delete droplets by tag
#' ## first, create a tag, then a droplet, then tag it
#' tag_create(name = "foobar")
#' e <- droplet_create()
#' tag_resource(name = "foobar", resource_id = e$id)
#' droplets(tag = "foobar")
#' ## then delete the droplet by tag name
#' droplet_delete(tag = "foobar")
#' }
droplet_delete <- function(droplet = NULL, tag = NULL, ...) {
  if (!is.null(droplet)) {
    droplet <- as.droplet(droplet)
    do_DELETE(sprintf('droplets/%s', droplet$id), ...)
  } else {
    do_DELETE('droplets', query = ascompact(list(tag_name = tag)), ...)
  }
}


#' Perform various actions on a droplet.
#'
#' These droplet actions have no further arguments.
#'
#' \describe{
#' \item{reboot}{This method allows you to reboot a droplet. This is
#'   the preferred method to use if a server is not responding}
#' \item{powercycle}{This method allows you to power cycle a droplet.
#'    This will turn off the droplet and then turn it back on.}
#' \item{shutdown}{Shutdown a running droplet. The droplet will remain in
#'   your account and you will continue to be charged for it.}
#' \item{power_off}{Shutdown a running droplet. The droplet will remain in
#'   your account and you will continue to be charged for it.}
#' \item{reset_password}{This method will reset the root password for a
#'   droplet. Please be aware that this will reboot the droplet to allow
#'   resetting the password.}
#' \item{enable_ipv6}{Enable IPv6 networking on an existing droplet (within
#'   a region that has IPv6 available).}
#' \item{enable_private_networking}{Enable private networking on an existing
#'   droplet (within a region that has private networking available)}
#' \item{disable_backups}{Disables backups for a droplet.}
#' \item{enable_backups}{Enables backups for a droplet.}
#' \item{power_on}{Turn on a droplet that's turned off.}
#' }
#' @inheritParams droplet_delete
#' @examples \dontrun{
#' d <- droplets()
#' d[[1]] %>% droplet_reboot()
#' d[[2]] %>% droplet_power_cycle()
#'
#' d <- droplet_create()
#' d %>% summary
#' d %>% droplet_enable_backups()
#' d %>% summary
#' }
#' @name droplet_action
NULL

#' @export
#' @rdname droplet_action
droplet_reboot <- function(droplet, ...) {
  droplet_action("reboot", droplet, ...)
}

#' @export
#' @rdname droplet_action
droplet_power_cycle <- function(droplet, ...) {
  droplet_action("power_cycle", droplet, ...)
}

#' @export
#' @rdname droplet_action
droplet_shutdown <- function(droplet, ...) {
  droplet_action("shutdown", droplet, ...)
}

#' @export
#' @rdname droplet_action
droplet_power_off <- function(droplet, ...) {
  droplet_action("power_off", droplet, ...)
}

#' @export
#' @rdname droplet_action
droplet_power_on <- function(droplet, ...) {
  droplet_action("power_on", droplet, ...)
}

#' @export
#' @rdname droplet_action
droplet_reset_password <- function(droplet, ...) {
  droplet_action("reset_password", droplet, ...)
}

#' @export
#' @rdname droplet_action
droplet_enable_ipv6 <- function(droplet, ...) {
  droplet_action("enable_ipv6", droplet, ...)
}

#' @export
#' @rdname droplet_action
droplet_enable_private_networking <- function(droplet, ...) {
  droplet_action("enable_private_networking", droplet, ...)
}

#' @export
#' @rdname droplet_action
droplet_enable_backups <- function(droplet, ...) {
  droplet_action("enable_backups", droplet, ...)
}

#' @export
#' @rdname droplet_action
droplet_disable_backups <- function(droplet, ...) {
  droplet_action("disable_backups", droplet, ...)
}

#' @export
#' @rdname droplet_action
droplet_upgrade <- function(droplet, ...) {
  droplet_action("migrate_droplet", droplet, ...)
}

droplet_action <- function(action, droplet, ...) {
  droplet <- as.droplet(droplet)

  res <- do_POST(sprintf('droplets/%s/actions', droplet$id),
    httr::content_type_json(),
    body = list(type = jsonlite::unbox(action), ...))
  as.action(res)
}


#' Modify a droplet.
#'
#' These methods allow you to modify existing droplets.
#'
#' \describe{
#' \item{resize}{Resize a specific droplet to a different size. This will
#'   affect the number of processors and memory allocated to the droplet.}
#' \item{rebuild}{Reinstall a droplet with a default image. This is useful
#'   if you want to start again but retain the same IP address for your
#'   droplet.}
#' \item{rename}{Change the droplet name}
#' \item{change_kernel}{Change kernel ID.}
#' }
#'
#' @inheritParams droplet_delete
#' @param size (character) Size slug (name) of the image size. See \code{sizes}
#' @details Beware: \code{droplet_resize()} does not seem to work, see
#' \code{resize()}
#' @examples \dontrun{
#' droplets()[[1]] %>% droplet_rename(name='newname')
#' }
#' @name droplet_modify
NULL

#' @export
#' @rdname droplet_modify
droplet_resize <- function(droplet, size, ...) {
  droplet_action("resize", droplet, size = jsonlite::unbox(size), ...)
}

#' @export
#' @rdname droplet_modify
#' @param image (optional) The image ID of the backup image that you would
#' like to restore.
droplet_rebuild <- function(droplet, image, ...) {
  droplet_action("rebuild", droplet, image = jsonlite::unbox(image), ...)
}

#' @export
#' @rdname droplet_modify
#' @param name (character) The new name for the droplet
droplet_rename <- function(droplet, name, ...) {
  droplet_action("rename", droplet, name = jsonlite::unbox(name), ...)
}

#' @export
#' @rdname droplet_modify
#' @param kernel (numeric) The ID of the new kernel.
droplet_change_kernel <- function(droplet, kernel, ...) {
  droplet_action("change_kernel", droplet, kernel = jsonlite::unbox(kernel),
    ...)
}

#' Take and restore snapshots.
#'
#' \describe{
#' \item{snapshot}{Take a snapshot of the droplet once it has been powered
#'   off, which can later be restored or used to create a new droplet from
#'   the same image.}
#' \item{snapshots_list}{List available snapshots}
#' \item{backups_list}{List available snapshots}
#' \item{restore}{Restore a droplet with a previous image or snapshot.
#'   This will be a mirror copy of the image or snapshot to your droplet. Be
#'   sure you have backed up any necessary information prior to restore.}
#' }
#'
#' @param droplet A droplet number or the result from a call to
#' \code{droplets()}
#' @param name (character) Optional. Name of the new snapshot you want to
#' create. If not set, the  snapshot name will default to the current date/time
#' @param image (optional) The image ID of the backup image that you would like
#' to restore.
#' @param wait If \code{TRUE} (default), wait until the snapshot has been
#' completed and is ready for use. If set to \code{FALSE} we return a
#' droplet object right away after droplet snapshot request has been sent.
#' @param ... Additional options passed down to \code{\link[httr]{POST}}
#' @examples \dontrun{
#' d <- droplet_create()
#' d %>% droplet_snapshots_list()
#' d %>% droplet_backups_list()
#'
#' d %>%
#'   droplet_snapshot() %>%
#'   droplet_power_on() %>%
#'   droplet_snapshots_list()
#'
#' # To delete safely
#' d %>%
#'   droplet_snapshot() %>%
#'   droplet_delete() %>%
#'   action_wait()
#' }
#' @export
droplet_snapshot <- function(droplet, name = NULL, wait = TRUE, ...) {
  droplet_status <- droplet$status
  droplet_power_off(droplet)
  droplet_wait(droplet)

  droplet_action("snapshot", droplet, name = name, ...)

  if (wait) {
    droplet_wait(droplet)
    droplet
  } else {
    droplet
  }
}

#' @export
#' @rdname droplet_snapshot
droplet_snapshots_list <- function(droplet, ...) {
  droplet <- as.droplet(droplet)

  res <- do_GET(sprintf('droplets/%s/snapshots', droplet$id), ...)
  list_to_object(res, "snapshot", class = "image")
}

#' @export
#' @rdname droplet_snapshot
droplet_restore <- function(droplet, image, ...) {
  droplet_action("restore", droplet, image = jsonlite::unbox(image), ...)
}

#' @export
#' @rdname droplet_snapshot
droplet_backups_list <- function(droplet, ...) {
  droplet <- as.droplet(droplet)

  res <- do_GET(sprintf('droplets/%s/backups', droplet$id), ...)
  res$backups
}


#' List all available kernels for a droplet.
#'
#' @export
#' @inheritParams droplet_delete
#' @examples \dontrun{
#' droplets()[[1]] %>% droplet_kernels_list
#' }
droplet_kernels_list <- function(droplet, ...) {
  droplet <- as.droplet(droplet)

  res <- do_GET(sprintf('droplets/%s/kernels', droplet$id), ...)
  res$kernels
}

#' List all droplets that are scheduled to be upgraded.
#'
#' @export
#' @param ... Additional options passed down to low-level API method.
#' @examples \dontrun{
#' droplet_upgrades_list()
#' }
droplet_upgrades_list <- function(...) do_GET('droplet_upgrades', ...)

#' Retrieve a droplet action or list all actions associatd with a droplet.
#'
#' @export
#' @inheritParams droplet_delete
#' @param actionid (integer) Optional. An action id.
#' @examples \dontrun{
#' droplet_actions(2428384)
#' droplet_actions(2428384, actionid=31223385)
#' }
droplet_actions <- function(droplet, actionid = NULL, ...) {
  droplet <- as.droplet(droplet)
  path <- if (is.null(actionid))
    sprintf('droplets/%s/actions', droplet$id)
  else
    sprintf('droplets/%s/actions/%s', droplet$id, actionid)
  res <- do_GET(path, ...)
  as.action(res)
}

#' List neighbors
#'
#' @export
#' @inheritParams droplet_delete
#' @examples \dontrun{
#' # List a droplet's neighbors on the same physical server
#' droplets()[[3]] %>% droplet_neighbors()
#' # List all neighbors on the same physical server
#' neighbors()
#' }
neighbors <- function(...) {
  res <- do_GET('reports/droplet_neighbors_ids', ...)
  res$neighbor_ids
}

#' @export
#' @rdname neighbors
droplet_neighbors <- function(droplet, ...) {
  droplet <- as.droplet(droplet)

  res <- do_GET(sprintf('droplets/%s/neighbors', droplet$id), ...)
  res$droplets
}

#' Get droplet's IP address
#'
#' @export
#' @inheritParams droplet_delete
#' @examples \dontrun{
#' # Obtain the droplet's IP as a string
#' my_droplet <- droplet_create("demo", region = "sfo3")
#' droplet_ip(my_droplet)
#' }
droplet_ip <- function(droplet) {
  v4 <- droplet$networks$v4
  if (length(v4) == 0) {
    stop("No network interface registered for this droplet\n  Try refreshing like: droplet(d$id)",
         call. = FALSE
    )
  }
  ips <- do.call("rbind", lapply(v4, as.data.frame))
  public_ip <- ips$type == "public"
  if (!any(public_ip)) {
    ip <- v4[[1]]$ip_address
  } else {
    ip <- ips$ip_address[public_ip][[1]]
  }
  ip
}
sckott/analogsea documentation built on July 18, 2023, 3:31 p.m.