Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions lib/facter/core/suitable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,6 @@ def weight
#
# @api private
def suitable?
unless defined? @suitable
@suitable = ! @confines.detect { |confine| ! confine.true? }
end

return @suitable
@confines.all? { |confine| confine.true? }
end
end
59 changes: 33 additions & 26 deletions lib/facter/ec2.rb
Original file line number Diff line number Diff line change
@@ -1,37 +1,44 @@
require 'facter/util/ec2'
require 'open-uri'
require 'facter/ec2/rest'

def metadata(id = "")
open("http://169.254.169.254/2008-02-01/meta-data/#{id||=''}").read.
split("\n").each do |o|
key = "#{id}#{o.gsub(/\=.*$/, '/')}"
if key[-1..-1] != '/'
value = open("http://169.254.169.254/2008-02-01/meta-data/#{key}").read.
split("\n")
symbol = "ec2_#{key.gsub(/\-|\//, '_')}".to_sym
Facter.add(symbol) { setcode { value.join(',') } }
else
metadata(key)
Facter.define_fact(:ec2_metadata) do
define_resolution(:rest) do
confine do
Facter.value(:virtual).match /^xen/
end

@querier = Facter::EC2::Metadata.new
confine do
@querier.reachable?
end

setcode do
@querier.fetch
end
end
rescue => details
Facter.warn "Could not retrieve ec2 metadata: #{details.message}"
end

def userdata()
Facter.add(:ec2_userdata) do
Facter.define_fact(:ec2_userdata) do
define_resolution(:rest) do
confine do
Facter.value(:virtual).match /^xen/
end

@querier = Facter::EC2::Userdata.new
confine do
@querier.reachable?
end

setcode do
if userdata = Facter::Util::EC2.userdata
userdata.split
end
@querier.fetch
end
end
end

if (Facter::Util::EC2.has_euca_mac? || Facter::Util::EC2.has_openstack_mac? ||
Facter::Util::EC2.has_ec2_arp?) && Facter::Util::EC2.can_connect?
metadata
userdata
else
Facter.debug "Not an EC2 host"
# The flattened version of the EC2 facts are deprecated and will be removed in
# a future release of Facter.
if (ec2_metadata = Facter.value(:ec2_metadata))
ec2_facts = Facter::Util::Values.flatten_structure("ec2", ec2_metadata)
ec2_facts.each_pair do |factname, factvalue|
Facter.add(factname, :value => factvalue)
end
end
129 changes: 129 additions & 0 deletions lib/facter/ec2/rest.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
require 'timeout'
require 'open-uri'

module Facter
module EC2
CONNECTION_ERRORS = [
Errno::EHOSTDOWN,
Errno::EHOSTUNREACH,
Errno::ENETUNREACH,
Errno::ECONNABORTED,
Errno::ECONNREFUSED,
Errno::ECONNRESET,
Errno::ETIMEDOUT,
]

class Base
def reachable?(retry_limit = 3)
timeout = 0.2
able_to_connect = false
attempts = 0

begin
Timeout.timeout(timeout) do
open(@baseurl).read
end
able_to_connect = true
rescue OpenURI::HTTPError => e
if e.message.match /404 Not Found/i
able_to_connect = false
else
retry if attempts < retry_limit
end
rescue Timeout::Error
retry if attempts < retry_limit
rescue *CONNECTION_ERRORS
retry if attempts < retry_limit
ensure
attempts = attempts + 1
end

able_to_connect
end
end

class Metadata < Base

DEFAULT_URI = "http://169.254.169.254/latest/meta-data/"

def initialize(uri = DEFAULT_URI)
@baseurl = uri
end

def fetch(path = '')
results = {}

keys = fetch_endpoint(path)
keys.each do |key|
if key.match(%r[/$])
# If a metadata key is suffixed with '/' then it's a general metadata
# resource, so we have to recursively look up all the keys in the given
# collection.
name = key[0..-2]
results[name] = fetch("#{path}#{key}")
else
# This is a simple key/value pair, we can just query the given endpoint
# and store the results.
ret = fetch_endpoint("#{path}#{key}")
results[key] = ret.size > 1 ? ret : ret.first
end
end

results
end

# @param path [String] The path relative to the object base url
#
# @return [Array, NilClass]
def fetch_endpoint(path)
uri = @baseurl + path
body = open(uri).read
parse_results(body)
rescue OpenURI::HTTPError => e
if e.message.match /404 Not Found/i
return nil
else
Facter.log_exception(e, "Failed to fetch ec2 uri #{uri}: #{e.message}")
return nil
end
rescue *CONNECTION_ERRORS => e
Facter.log_exception(e, "Failed to fetch ec2 uri #{uri}: #{e.message}")
return nil
end

private

def parse_results(body)
lines = body.split("\n")
lines.map do |line|
if (match = line.match(/^(\d+)=.*$/))
# Metadata arrays are formatted like '<index>=<associated key>/', so
# we need to extract the index from that output.
"#{match[1]}/"
else
line
end
end
end
end

class Userdata < Base
DEFAULT_URI = "http://169.254.169.254/latest/user-data/"

def initialize(uri = DEFAULT_URI)
@baseurl = uri
end

def fetch
open(@baseurl).read
rescue OpenURI::HTTPError => e
if e.message.match /404 Not Found/i
return nil
else
Facter.log_exception(e, "Failed to fetch ec2 uri #{uri}: #{e.message}")
return nil
end
end
end
end
end
5 changes: 5 additions & 0 deletions lib/facter/util/ec2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class << self
# The +wait_sec+ parameter provides you with an adjustable timeout.
#
def can_connect?(wait_sec=2)
Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead")
url = "http://169.254.169.254:80/"
Timeout::timeout(wait_sec) {open(url)}
return true
Expand All @@ -23,19 +24,22 @@ def can_connect?(wait_sec=2)
# Test if this host has a mac address used by Eucalyptus clouds, which
# normally is +d0:0d+.
def has_euca_mac?
Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead")
!!(Facter.value(:macaddress) =~ %r{^[dD]0:0[dD]:})
end

# Test if this host has a mac address used by OpenStack, which
# normally starts with FA:16:3E (older versions of OpenStack
# may generate mac addresses starting with 02:16:3E)
def has_openstack_mac?
Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead")
!!(Facter.value(:macaddress) =~ %r{^(02|[fF][aA]):16:3[eE]})
end

# Test if the host has an arp entry in its cache that matches the EC2 arp,
# which is normally +fe:ff:ff:ff:ff:ff+.
def has_ec2_arp?
Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead")
kernel = Facter.value(:kernel)

mac_address_re = case kernel
Expand Down Expand Up @@ -73,6 +77,7 @@ def has_ec2_arp?
#
# @return [String] containing the response body or `nil`
def self.userdata(version="latest")
Facter.warnonce("#{self}.#{__method__} is deprecated; see the Facter::EC2 classes instead")
uri = "http://169.254.169.254/#{version}/user-data/"
begin
read_uri(uri)
Expand Down
29 changes: 29 additions & 0 deletions lib/facter/util/values.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

module Facter
module Util
# A util module for facter containing helper methods
Expand Down Expand Up @@ -75,6 +76,34 @@ def convert(value)
value = value.downcase if value.is_a?(String)
value
end

# Flatten the given data structure to something that's suitable to return
# as flat facts.
#
# @param path [String] The fact path to be prefixed to the given value.
# @param structure [Object] The data structure to flatten. Nested hashes
# will be recursively flattened, everything else will be returned as-is.
#
# @return [Hash] The given data structure prefixed with the given path
def flatten_structure(path, structure)
results = {}

if structure.is_a? Hash
structure.each_pair do |name, value|
new_path = "#{path}_#{name}".gsub(/\-|\//, '_')
results.merge! flatten_structure(new_path, value)
end
elsif structure.is_a? Array
structure.each_with_index do |value, index|
new_path = "#{path}_#{index}"
results.merge! flatten_structure(new_path, value)
end
else
results[path] = structure
end

results
end
end
end
end
20 changes: 20 additions & 0 deletions spec/fixtures/unit/ec2/rest/meta-data/root
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
ami-id
ami-launch-index
ami-manifest-path
block-device-mapping/
hostname
instance-action
instance-id
instance-type
kernel-id
local-hostname
local-ipv4
mac
metrics/
network/
placement/
profile
public-hostname
public-ipv4
public-keys/
reservation-id
10 changes: 10 additions & 0 deletions spec/unit/core/suitable_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,15 @@ def initialize

expect(subject).to_not be_suitable
end

it "recalculates suitability on every invocation" do
subject.confine :kernel => 'Linux'

subject.confines.first.stubs(:true?).returns false
expect(subject).to_not be_suitable
subject.confines.first.unstub(:true?)
subject.confines.first.stubs(:true?).returns true
expect(subject).to be_suitable
end
end
end
Loading