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 -