module Ohai::Mixin::Ec2Metadata

This code parses the EC2 Instance Metadata API to provide details of the running instance.

Earlier version of this code assumed a specific version of the metadata API was available. Unfortunately the API versions supported by a particular instance are determined at instance launch and are not extended over the life of the instance. As such the earlier code would fail depending on the age of the instance.

The updated code probes the instance metadata endpoint for available versions, determines the most advanced version known to work and executes the metadata retrieval using that version.

If no compatible version is found, an empty hash is returned.

Constants

EC2_ARRAY_DIR
EC2_ARRAY_VALUES
EC2_JSON_DIR
EC2_METADATA_ADDR
EC2_SUPPORTED_VERSIONS

Public Instance Methods

best_api_version() click to toggle source
# File lib/ohai/mixin/ec2_metadata.rb, line 77
def best_api_version
  response = http_client.get("/")
  unless response.code == '200'
    raise "Unable to determine EC2 metadata version (returned #{response.code} response)"
  end
  # Note: Sorting the list of versions may have unintended consequences in
  # non-EC2 environments. It appears to be safe in EC2 as of 2013-04-12.
  versions = response.body.split("\n")
  versions = response.body.split("\n").sort
  until (versions.empty? || EC2_SUPPORTED_VERSIONS.include?(versions.last)) do
    pv = versions.pop
    Ohai::Log.debug("EC2 shows unsupported metadata version: #{pv}") unless pv == 'latest'
  end
  Ohai::Log.debug("EC2 metadata version: #{versions.last}")
  if versions.empty?
    raise "Unable to determine EC2 metadata version (no supported entries found)"
  end
  versions.last
end
can_metadata_connect?(addr, port, timeout=2) click to toggle source
# File lib/ohai/mixin/ec2_metadata.rb, line 51
def can_metadata_connect?(addr, port, timeout=2)
  t = Socket.new(Socket::Constants::AF_INET, Socket::Constants::SOCK_STREAM, 0)
  saddr = Socket.pack_sockaddr_in(port, addr)
  connected = false

  begin
    t.connect_nonblock(saddr)
  rescue Errno::EINPROGRESS
    r,w,e = IO::select(nil,[t],nil,timeout)
    if !w.nil?
      connected = true
    else
      begin
        t.connect_nonblock(saddr)
      rescue Errno::EISCONN
        t.close
        connected = true
      rescue SystemCallError
      end
    end
  rescue SystemCallError
  end
  Ohai::Log.debug("can_metadata_connect? == #{connected}")
  connected
end
fetch_dir_metadata(id, api_version) click to toggle source
# File lib/ohai/mixin/ec2_metadata.rb, line 137
def fetch_dir_metadata(id, api_version)
  metadata = Hash.new
    metadata_get(id, api_version).body.split("\n").each do |o|
    key = expand_path(o)
    if key[-1..-1] != '/'
      metadata[metadata_key(key)] = metadata_get("#{id}#{key}", api_version).body
    elsif not key.eql?('/')
      metadata[key[0..-2]] = fetch_dir_metadata("#{id}#{key}", api_version)
    end
  end
  metadata
end
fetch_json_dir_metadata(id, api_version) click to toggle source
# File lib/ohai/mixin/ec2_metadata.rb, line 150
def fetch_json_dir_metadata(id, api_version)
  metadata = Hash.new
  metadata_get(id, api_version).body.split("\n").each do |o|
    key = expand_path(o)
    if key[-1..-1] != '/'
      data = metadata_get("#{id}#{key}", api_version).body
      json = StringIO.new(data)
      parser = Yajl::Parser.new
      metadata[metadata_key(key)] = parser.parse(json)
    elsif not key.eql?('/')
      metadata[key[0..-2]] = fetch_json_dir_metadata("#{id}#{key}", api_version)
    end
  end
  metadata
end
fetch_metadata(id='', api_version=nil) click to toggle source
# File lib/ohai/mixin/ec2_metadata.rb, line 109
def fetch_metadata(id='', api_version=nil)
  api_version ||= best_api_version
  return Hash.new if api_version.nil?
  metadata = Hash.new
  metadata_get(id, api_version).body.split("\n").each do |o|
    key = expand_path("#{id}#{o}")
    if key[-1..-1] != '/'
      metadata[metadata_key(key)] =
        if EC2_ARRAY_VALUES.include? key
          metadata_get(key, api_version).body.split("\n")
        else
          metadata_get(key, api_version).body
        end
    elsif not key.eql?(id) and not key.eql?('/')
      name = key[0..-2]
      sym = metadata_key(name)
      if EC2_ARRAY_DIR.include?(name)
        metadata[sym] = fetch_dir_metadata(key, api_version)
      elsif EC2_JSON_DIR.include?(name)
        metadata[sym] = fetch_json_dir_metadata(key, api_version)
      else
        fetch_metadata(key, api_version).each{|k,v| metadata[k] = v}
      end
    end
  end
  metadata
end
fetch_userdata() click to toggle source
# File lib/ohai/mixin/ec2_metadata.rb, line 166
def fetch_userdata()
  api_version = best_api_version
  return nil if api_version.nil?
  response = http_client.get("/#{api_version}/user-data/")
  response.code == "200" ? response.body : nil
end
http_client() click to toggle source
# File lib/ohai/mixin/ec2_metadata.rb, line 97
def http_client
  Net::HTTP.start(EC2_METADATA_ADDR).tap {|h| h.read_timeout = 600}
end
metadata_get(id, api_version) click to toggle source
# File lib/ohai/mixin/ec2_metadata.rb, line 101
def metadata_get(id, api_version)
  response = http_client.get("/#{api_version}/meta-data/#{id}")
  unless response.code == '200'
    raise "Encountered error retrieving EC2 metadata (returned #{response.code} response)"
  end
  response
end

Private Instance Methods

expand_path(file_name) click to toggle source
# File lib/ohai/mixin/ec2_metadata.rb, line 175
def expand_path(file_name)
  path = file_name.gsub(/\=.*$/, '/')
  # ignore "./" and "../"
  path.gsub(%r{/\.\.?(?:/|$)}, '/').
    sub(%r{^\.\.?(?:/|$)}, '').
    sub(%r{^$}, '/')
end
metadata_key(key) click to toggle source
# File lib/ohai/mixin/ec2_metadata.rb, line 183
def metadata_key(key)
  key.gsub(/\-|\//, '_')
end