module Cult::Drivers::Common

Public Class Methods

included(cls) click to toggle source
# File lib/cult/drivers/common.rb, line 173
def self.included(cls)
  cls.extend(ClassMethods)
  cls.include(::Cult::Transaction)
end

Public Instance Methods

await_ssh(host) click to toggle source

Waits until SSH is available at host. “available” jsut means “listening”/acceping connections.

# File lib/cult/drivers/common.rb, line 116
def await_ssh(host)
  puts "Awaiting sshd on #{host}"
  backoff_loop do
    begin
      sock = connect_timeout(host, 22, 1)
      break
    rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
           Errno::EHOSTDOWN
      # Nothing, these are expected
    ensure
      sock.close if sock
    end
  end
end
backoff_loop(wait = 3, scale = 1.2) { |times, total_wait| ... } click to toggle source

Does back-off retrying. Defaults to not-exponential. Block must throw :done to signal they are done.

# File lib/cult/drivers/common.rb, line 100
def backoff_loop(wait = 3, scale = 1.2, &block)
  times = 0
  total_wait = 0.0

  loop do
    yield times, total_wait
    sleep wait
    times += 1
    total_wait += wait
    wait *= scale
  end
end
connect_timeout(host, port, timeout = 5) click to toggle source

This should not be needed, but it is: spin.atomicobject.com/2013/09/30/socket-connection-timeout-ruby/

# File lib/cult/drivers/common.rb, line 134
def connect_timeout(host, port, timeout = 5)
  # Convert the passed host into structures the non-blocking calls
  # can deal with
  addr = Socket.getaddrinfo(host, nil)
  sockaddr = Socket.pack_sockaddr_in(port, addr[0][3])

  Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0).tap do |socket|
    socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)

    begin
      # Initiate the socket connection in the background. If it doesn't
      # fail immediately it will raise an IO::WaitWritable
      # (Errno::EINPROGRESS) indicating the connection is in progress.
      socket.connect_nonblock(sockaddr)

    rescue IO::WaitWritable
      # IO.select will block until the socket is writable or the timeout
      # is exceeded - whichever comes first.
      if IO.select(nil, [socket], nil, timeout)
        begin
          # Verify there is now a good connection
          socket.connect_nonblock(sockaddr)
        rescue Errno::EISCONN
          # Good news everybody, the socket is connected!
        rescue
          # An unexpected exception was raised - the connection is no good.
          socket.close
          raise
        end
      else
        # IO.select returns nil when the socket is not ready before
        # timeout seconds have elapsed
        socket.close
        raise Errno::ETIMEDOUT
      end
    end
  end
end
distro_name(s) click to toggle source
# File lib/cult/drivers/common.rb, line 86
def distro_name(s)
  s = s.gsub(/\bx64\b/i, '')
  # People sometimes add "LTS" to the name of Ubuntu LTS releases
  s = s.gsub(/\blts\b/i, '') if s.match(/ubuntu/i)

  # We don't particularly need the debian codename
  s = s.gsub(/(\d)[\s-]+(\S+)/, '\1') if s.match(/^debian/i)
  s = s.gsub(/[\s.]+/, '-')
  s.downcase
end
fetch_mapped(name:, from:, key:) click to toggle source

works with with_id_mapping to convert a human-readible/normalized key to the id the backend service expects. Allows '=value' to force a literal value, and gives better error messages.

# File lib/cult/drivers/common.rb, line 45
def fetch_mapped(name:, from:, key:)
  # Allow for the override.
  key = key.to_s
  return key[1..-1] if key[0] == '='

  begin
    from.fetch(key)
  rescue KeyError => e
    raise ArgumentError, "Invalid #{name}: \"#{key}\".  " +
                         "Use \"=#{key}\" to force, or use one of: " +
                         from.keys.inspect
  end
end
slugify(s) click to toggle source
# File lib/cult/drivers/common.rb, line 81
def slugify(s)
  s.gsub(/[^a-z0-9]+/i, '-').gsub(/(^\-)|(-\z)/, '').downcase
end
ssh_key_info(data: nil, file: nil) click to toggle source
# File lib/cult/drivers/common.rb, line 60
def ssh_key_info(data: nil, file: nil)
  if data.nil?
    fail ArgumentError if file.nil?
    data = File.read(file)
  else
    fail ArgumentError unless file.nil?
  end

  data = data.chomp
  key = Net::SSH::KeyFactory.load_data_public_key(data, file)

  fields = data.split(/ /)
  return {
    name:        fields[-1],
    fingerprint: key.fingerprint,
    data:        data,
    file:        file
  }
end