lib/ohai/mixin/ec2_metadata.rb in ohai-6.16.0 vs lib/ohai/mixin/ec2_metadata.rb in ohai-6.18.0.rc.0
- old
+ new
@@ -20,15 +20,32 @@
require 'net/http'
require 'socket'
module Ohai
module Mixin
+ ##
+ # 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.
+ #
module Ec2Metadata
EC2_METADATA_ADDR = "169.254.169.254" unless defined?(EC2_METADATA_ADDR)
- EC2_METADATA_URL = "/2012-01-12/meta-data" unless defined?(EC2_METADATA_URL)
- EC2_USERDATA_URL = "/2012-01-12/user-data" unless defined?(EC2_USERDATA_URL)
+ EC2_SUPPORTED_VERSIONS = %w[ 1.0 2007-01-19 2007-03-01 2007-08-29 2007-10-10 2007-12-15
+ 2008-02-01 2008-09-01 2009-04-04 2011-01-01 2011-05-01 2012-01-12 ]
+
EC2_ARRAY_VALUES = %w(security-groups)
EC2_ARRAY_DIR = %w(network/interfaces/macs)
EC2_JSON_DIR = %w(iam)
def can_metadata_connect?(addr, port, timeout=2)
@@ -55,71 +72,103 @@
end
Ohai::Log.debug("can_metadata_connect? == #{connected}")
connected
end
+ 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
+
def http_client
Net::HTTP.start(EC2_METADATA_ADDR).tap {|h| h.read_timeout = 600}
end
- def fetch_metadata(id='')
+ 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
+
+ def fetch_metadata(id='', api_version=nil)
+ api_version ||= best_api_version
+ return Hash.new if api_version.nil?
metadata = Hash.new
- http_client.get("#{EC2_METADATA_URL}/#{id}").body.split("\n").each do |o|
+ 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
- http_client.get("#{EC2_METADATA_URL}/#{key}").body.split("\n")
+ metadata_get(key, api_version).body.split("\n")
else
- http_client.get("#{EC2_METADATA_URL}/#{key}").body
+ metadata_get(key, api_version).body
end
elsif not key.eql?(id) and not key.eql?('/')
name = key[0..-2]
- sym = metadata_key(name)
+ sym = metadata_key(name)
if EC2_ARRAY_DIR.include?(name)
- metadata[sym] = fetch_dir_metadata(key)
+ metadata[sym] = fetch_dir_metadata(key, api_version)
elsif EC2_JSON_DIR.include?(name)
- metadata[sym] = fetch_json_dir_metadata(key)
+ metadata[sym] = fetch_json_dir_metadata(key, api_version)
else
- fetch_metadata(key).each{|k,v| metadata[k] = v}
+ fetch_metadata(key, api_version).each{|k,v| metadata[k] = v}
end
end
end
metadata
end
- def fetch_dir_metadata(id)
+ def fetch_dir_metadata(id, api_version)
metadata = Hash.new
- http_client.get("#{EC2_METADATA_URL}/#{id}").body.split("\n").each do |o|
+ metadata_get(id, api_version).body.split("\n").each do |o|
key = expand_path(o)
if key[-1..-1] != '/'
- metadata[metadata_key(key)] = http_client.get("#{EC2_METADATA_URL}/#{id}#{key}").body
+ metadata[metadata_key(key)] = metadata_get("#{id}#{key}", api_version).body
elsif not key.eql?('/')
- metadata[key[0..-2]] = fetch_dir_metadata("#{id}#{key}")
+ metadata[key[0..-2]] = fetch_dir_metadata("#{id}#{key}", api_version)
end
end
metadata
end
- def fetch_json_dir_metadata(id)
+ def fetch_json_dir_metadata(id, api_version)
metadata = Hash.new
- http_client.get("#{EC2_METADATA_URL}/#{id}").body.split("\n").each do |o|
+ metadata_get(id, api_version).body.split("\n").each do |o|
key = expand_path(o)
if key[-1..-1] != '/'
- data = http_client.get("#{EC2_METADATA_URL}/#{id}#{key}").body
+ 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}")
+ metadata[key[0..-2]] = fetch_json_dir_metadata("#{id}#{key}", api_version)
end
end
metadata
end
def fetch_userdata()
- response = http_client.get("#{EC2_USERDATA_URL}/")
+ 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
private
@@ -136,6 +185,5 @@
end
end
end
end
-