class Cult::Drivers::DigitalOceanDriver

Attributes

client[R]

Public Class Methods

new(api_key:) click to toggle source
# File lib/cult/drivers/digital_ocean_driver.rb, line 11
def initialize(api_key:)
  @client = DropletKit::Client.new(access_token: api_key)
end
setup!() click to toggle source
Calls superclass method Cult::Driver::setup!
# File lib/cult/drivers/digital_ocean_driver.rb, line 139
def self.setup!
  super
  url = "https://cloud.digitalocean.com/settings/api/tokens/new"

  puts "Cult needs a read/write Access Token created for your " +
       "DigitalOcean account."
  puts "One can be generated at the following URL:"
  puts
  puts "  #{url}"
  puts

  CLI.launch_browser(url) if CLI.yes_no?("Open Browser?")

  api_key = CLI.prompt("Access Token")
  unless api_key.match(/\A[0-9a-f]{64}\z/)
    puts "That doesn't look like an access token, but we'll take it."
  end

  inst = new(api_key: api_key)

  return {
    api_key: api_key,
    driver: driver_name,
    configurations: {
      sizes:  inst.sizes,
      zones:  inst.zones,
      images: inst.images,
    }
  }
end

Public Instance Methods

await_creation(droplet) click to toggle source
# File lib/cult/drivers/digital_ocean_driver.rb, line 57
def await_creation(droplet)
  d = nil
  backoff_loop do
    d = client.droplets.find(id: droplet.id)
    break if d.status == 'active'
  end
  return d
end
destroy!(id:, ssh_key_id: nil) click to toggle source
# File lib/cult/drivers/digital_ocean_driver.rb, line 67
def destroy!(id:, ssh_key_id: nil)
  client.droplets.delete(id: id)
  destroy_ssh_key!(ssh_key_id: ssh_key_id) if ssh_key_id
end
destroy_ssh_key!(ssh_key_id:) click to toggle source
# File lib/cult/drivers/digital_ocean_driver.rb, line 72
def destroy_ssh_key!(ssh_key_id:)
  client.ssh_keys.delete(id: ssh_key_id)
end
images_map() click to toggle source
# File lib/cult/drivers/digital_ocean_driver.rb, line 25
def images_map
  distros = %w(ubuntu coreos centos freebsd fedora debian).join '|'
  re = /^(#{distros})\-.*\-x64$/
  client.images.all.to_a.select do |image|
    image.public && image.slug && image.slug.match(re)
  end.map do |image|
    [slugify(distro_name(image.slug)), image.slug]
  end.to_h
end
provision!(name:, size:, zone:, image:, ssh_public_key:) click to toggle source
# File lib/cult/drivers/digital_ocean_driver.rb, line 76
def provision!(name:, size:, zone:, image:, ssh_public_key:)
  transaction do |xac|
    ssh_key_id = upload_ssh_key(file: ssh_public_key)
    xac.rollback do
      destroy_ssh_key!(ssh_key_id: ssh_key_id)
    end

    begin
      params = {
        name:     name,
        size:     fetch_mapped(name: :size, from: sizes_map, key: size),
        image:    fetch_mapped(name: :image, from: images_map, key: image),
        region:   fetch_mapped(name: :zone, from: zones_map, key: zone),
        ssh_keys: [ssh_key_id],

        private_networking: true,
        ipv6: true
      }
    rescue KeyError => e
      fail ArgumentError, "Invalid argument: #{e.message}"
    end

    droplet = DropletKit::Droplet.new(params)

    if droplet.nil?
      fail "Droplet was nil: #{params.inspect}"
    end

    droplet = client.droplets.create(droplet)
    xac.rollback do
      destroy!(id: droplet.id)
    end

    droplet = await_creation(droplet)

    ipv4_public  = droplet.networks.v4.find {|n| n.type == 'public'  }
    ipv4_private = droplet.networks.v4.find {|n| n.type == 'private' }
    ipv6_public  = droplet.networks.v6.find {|n| n.type == 'public'  }
    ipv6_private = droplet.networks.v6.find {|n| n.type == 'private' }

    await_ssh(ipv4_public.ip_address)
    return {
        name:          droplet.name,
        size:          size,
        zone:          zone,
        image:         image,

        ssh_key_id:    ssh_key_id,

        id:            droplet.id,
        created_at:    droplet.created_at,
        host:          ipv4_public&.ip_address,
        ipv4_public:   ipv4_public&.ip_address,
        ipv4_private:  ipv4_private&.ip_address,
        ipv6_public:   ipv6_public&.ip_address,
        ipv6_private:  ipv6_private&.ip_address,
        # Get rid of magic in droplet.
        meta:          JSON.parse(droplet.to_json)
    }
  end
end
sizes_map() click to toggle source
# File lib/cult/drivers/digital_ocean_driver.rb, line 16
def sizes_map
  client.sizes.all.to_a.map do |s|
    [s.slug, s.slug]
  end.to_h
end
upload_ssh_key(file:) click to toggle source
# File lib/cult/drivers/digital_ocean_driver.rb, line 48
def upload_ssh_key(file:)
  key = ssh_key_info(file: file)
  # If we already have one with this fingerprint, use it.
  dk_key = DropletKit::SSHKey.new(public_key: key[:data],
                                  name: "Cult: #{key[:name]}")
  client.ssh_keys.create(dk_key).id
end
zones_map() click to toggle source
# File lib/cult/drivers/digital_ocean_driver.rb, line 38
def zones_map
  client.regions.all.map do |zone|
    [zone.slug, zone.slug]
  end.to_h
end