diff --git a/lib/facter/core/suitable.rb b/lib/facter/core/suitable.rb index 1b3c04fceb..0262470919 100644 --- a/lib/facter/core/suitable.rb +++ b/lib/facter/core/suitable.rb @@ -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 diff --git a/lib/facter/ec2.rb b/lib/facter/ec2.rb index 09e0109528..2e575927cd 100644 --- a/lib/facter/ec2.rb +++ b/lib/facter/ec2.rb @@ -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 diff --git a/lib/facter/ec2/rest.rb b/lib/facter/ec2/rest.rb new file mode 100644 index 0000000000..e9c1fcadde --- /dev/null +++ b/lib/facter/ec2/rest.rb @@ -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 '=/', 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 diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb index c6e5dcadd4..b81c8febe8 100644 --- a/lib/facter/util/ec2.rb +++ b/lib/facter/util/ec2.rb @@ -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 @@ -23,6 +24,7 @@ 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 @@ -30,12 +32,14 @@ def has_euca_mac? # 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 @@ -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) diff --git a/lib/facter/util/values.rb b/lib/facter/util/values.rb index a7048d54da..1fbb1b686e 100644 --- a/lib/facter/util/values.rb +++ b/lib/facter/util/values.rb @@ -1,3 +1,4 @@ + module Facter module Util # A util module for facter containing helper methods @@ -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 diff --git a/spec/fixtures/unit/ec2/rest/meta-data/root b/spec/fixtures/unit/ec2/rest/meta-data/root new file mode 100644 index 0000000000..9ec3bbedad --- /dev/null +++ b/spec/fixtures/unit/ec2/rest/meta-data/root @@ -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 diff --git a/spec/unit/core/suitable_spec.rb b/spec/unit/core/suitable_spec.rb index 4c0b1fde06..8277408799 100644 --- a/spec/unit/core/suitable_spec.rb +++ b/spec/unit/core/suitable_spec.rb @@ -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 diff --git a/spec/unit/ec2/rest_spec.rb b/spec/unit/ec2/rest_spec.rb new file mode 100644 index 0000000000..5c74b495f4 --- /dev/null +++ b/spec/unit/ec2/rest_spec.rb @@ -0,0 +1,140 @@ +require 'spec_helper' +require 'facter/ec2/rest' + +shared_examples_for "an ec2 rest querier" do + describe "determining if the uri is reachable" do + it "retries if the connection times out" do + subject.stubs(:open).returns(stub(:read => nil)) + Timeout.expects(:timeout).with(0.2).twice.raises(Timeout::Error).returns(true) + expect(subject).to be_reachable + end + + it "retries if the connection is reset" do + subject.expects(:open).twice.raises(Errno::ECONNREFUSED).returns(StringIO.new("woo")) + expect(subject).to be_reachable + end + + it "is false if the given uri returns a 404" do + subject.expects(:open).with(anything).once.raises(OpenURI::HTTPError.new("404 Not Found", StringIO.new("woo"))) + expect(subject).to_not be_reachable + end + end + +end + +describe Facter::EC2::Metadata do + + subject { described_class.new('http://0.0.0.0/latest/meta-data/') } + + let(:response) { StringIO.new } + + describe "fetching a metadata endpoint" do + it "splits the body into an array" do + response.string = my_fixture_read("meta-data/root") + subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/").returns response + output = subject.fetch_endpoint('') + + expect(output).to eq %w[ + 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 + ] + end + + it "reformats keys that are array indices" do + response.string = "0=adrien@grey/" + subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/public-keys/").returns response + output = subject.fetch_endpoint("public-keys/") + + expect(output).to eq %w[0/] + end + + it "returns nil if the endpoint returns a 404" do + Facter.expects(:log_exception).never + subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/public-keys/1/").raises OpenURI::HTTPError.new("404 Not Found", response) + output = subject.fetch_endpoint('public-keys/1/') + + expect(output).to be_nil + end + + it "logs an error if the endpoint raises a non-404 HTTPError" do + Facter.expects(:log_exception).with(instance_of(OpenURI::HTTPError), anything) + + subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/").raises OpenURI::HTTPError.new("418 I'm a Teapot", response) + output = subject.fetch_endpoint("") + + expect(output).to be_nil + end + + it "logs an error if the endpoint raises a connection error" do + Facter.expects(:log_exception).with(instance_of(Errno::ECONNREFUSED), anything) + + subject.stubs(:open).with("http://0.0.0.0/latest/meta-data/").raises Errno::ECONNREFUSED + output = subject.fetch_endpoint('') + + expect(output).to be_nil + end + end + + describe "recursively fetching the EC2 metadata API" do + it "queries the given endpoint for metadata keys" do + subject.expects(:fetch_endpoint).with("").returns([]) + subject.fetch + end + + it "fetches the value for a simple metadata key" do + subject.expects(:fetch_endpoint).with("").returns(['indexthing']) + subject.expects(:fetch_endpoint).with("indexthing").returns(['first', 'second']) + + output = subject.fetch + expect(output).to eq({'indexthing' => ['first', 'second']}) + end + + it "unwraps metadata values that are in single element arrays" do + subject.expects(:fetch_endpoint).with("").returns(['ami-id']) + subject.expects(:fetch_endpoint).with("ami-id").returns(['i-12x']) + + output = subject.fetch + expect(output).to eq({'ami-id' => 'i-12x'}) + end + + it "recursively queries an endpoint if the key ends with '/'" do + subject.expects(:fetch_endpoint).with("").returns(['metrics/']) + subject.expects(:fetch_endpoint).with("metrics/").returns(['vhostmd']) + subject.expects(:fetch_endpoint).with("metrics/vhostmd").returns(['woo']) + + output = subject.fetch + expect(output).to eq({'metrics' => {'vhostmd' => 'woo'}}) + end + end + + it_behaves_like "an ec2 rest querier" +end + +describe Facter::EC2::Userdata do + + subject { described_class.new('http://0.0.0.0/latest/user-data/') } + + let(:response) { StringIO.new } + + describe "reaching the userdata" do + it "queries the userdata URI" do + subject.expects(:open).with('http://0.0.0.0/latest/user-data/').returns(response) + subject.fetch + end + + it "returns the result of the query without modification" do + response.string = "clooouuuuud" + subject.expects(:open).with('http://0.0.0.0/latest/user-data/').returns(response) + expect(subject.fetch).to eq "clooouuuuud" + end + + it "is nil if the URI returned a 404" do + subject.expects(:open).with('http://0.0.0.0/latest/user-data/').once.raises(OpenURI::HTTPError.new("404 Not Found", StringIO.new("woo"))) + expect(subject.fetch).to be_nil + end + end + + it_behaves_like "an ec2 rest querier" +end diff --git a/spec/unit/ec2_spec.rb b/spec/unit/ec2_spec.rb index f26a61316f..bf2aa2f5ea 100755 --- a/spec/unit/ec2_spec.rb +++ b/spec/unit/ec2_spec.rb @@ -1,187 +1,117 @@ -#! /usr/bin/env ruby - require 'spec_helper' -require 'facter/util/ec2' - -describe "ec2 facts" do - # This is the standard prefix for making an API call in EC2 (or fake) - # environments. - let(:api_prefix) { "http://169.254.169.254" } - - describe "when running on ec2" do - before :each do - # This is an ec2 instance, not a eucalyptus instance - Facter::Util::EC2.stubs(:has_euca_mac?).returns(false) - Facter::Util::EC2.stubs(:has_openstack_mac?).returns(false) - Facter::Util::EC2.stubs(:has_ec2_arp?).returns(true) - - # Assume we can connect - Facter::Util::EC2.stubs(:can_connect?).returns(true) - end - - it "should create flat meta-data facts" do - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.returns(StringIO.new("foo")) +require 'facter/ec2/rest' - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/foo"). - at_least_once.returns(StringIO.new("bar")) +describe "ec2_metadata" do + let(:querier) { stub('EC2 metadata querier') } - Facter.collection.internal_loader.load(:ec2) - - Facter.fact(:ec2_foo).value.should == "bar" - end + before do + Facter::EC2::Metadata.stubs(:new).returns querier + Facter.collection.internal_loader.load(:ec2) + end - it "should create flat meta-data facts with comma seperation" do - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.returns(StringIO.new("foo")) + subject { Facter.fact(:ec2_metadata).resolution(:rest) } - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/foo"). - at_least_once.returns(StringIO.new("bar\nbaz")) + it "is unsuitable if the virtual fact is not xen" do + Facter.fact(:virtual).stubs(:value).returns "kvm" + expect(subject).to_not be_suitable + end - Facter.collection.internal_loader.load(:ec2) + it "is unsuitable if ec2 endpoint is not reachable" do + Facter.fact(:virtual).stubs(:value).returns "xen" + querier.stubs(:reachable?).returns false + expect(subject).to_not be_suitable + end - Facter.fact(:ec2_foo).value.should == "bar,baz" + describe "when the ec2 endpoint is reachable" do + before do + querier.stubs(:reachable?).returns true end - it "should create structured meta-data facts" do - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.returns(StringIO.new("foo/")) - - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/foo/"). - at_least_once.returns(StringIO.new("bar")) + it "is suitable if the virtual fact is xen" do + Facter.fact(:virtual).stubs(:value).returns "xen" + subject.suitable? - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/foo/bar"). - at_least_once.returns(StringIO.new("baz")) - - Facter.collection.internal_loader.load(:ec2) - - Facter.fact(:ec2_foo_bar).value.should == "baz" + expect(subject).to be_suitable end - it "should create ec2_user_data fact" do - # No meta-data - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.returns(StringIO.new("")) - - Facter::Util::EC2.stubs(:read_uri). - with("#{api_prefix}/latest/user-data/"). - returns("test") - - Facter.collection.internal_loader.load(:ec2) - Facter.fact(:ec2_userdata).value.should == ["test"] + it "is suitable if the virtual fact is xenu" do + Facter.fact(:virtual).stubs(:value).returns "xenu" + expect(subject).to be_suitable end end - describe "when running on eucalyptus" do - before :each do - # Return false for ec2, true for eucalyptus - Facter::Util::EC2.stubs(:has_euca_mac?).returns(true) - Facter::Util::EC2.stubs(:has_openstack_mac?).returns(false) - Facter::Util::EC2.stubs(:has_ec2_arp?).returns(false) - - # Assume we can connect - Facter::Util::EC2.stubs(:can_connect?).returns(true) - end + it "resolves the value by recursively querying the rest endpoint" do + querier.expects(:fetch).returns({"hello" => "world"}) + expect(subject.value).to eq({"hello" => "world"}) + end +end - it "should create ec2_user_data fact" do - # No meta-data - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/meta-data/").\ - at_least_once.returns(StringIO.new("")) +describe "ec2_userdata" do + let(:querier) { stub('EC2 metadata querier') } - Facter::Util::EC2.stubs(:read_uri). - with("#{api_prefix}/latest/user-data/"). - returns("test") + before do + Facter::EC2::Userdata.stubs(:new).returns querier + Facter.collection.internal_loader.load(:ec2) + end - # Force a fact load - Facter.collection.internal_loader.load(:ec2) + subject { Facter.fact(:ec2_userdata).resolution(:rest) } - Facter.fact(:ec2_userdata).value.should == ["test"] - end + it "is unsuitable if the virtual fact is not xen" do + Facter.fact(:virtual).stubs(:value).returns "kvm" + expect(subject).to_not be_suitable end - describe "when running on openstack" do - before :each do - # Return false for ec2, true for eucalyptus - Facter::Util::EC2.stubs(:has_openstack_mac?).returns(true) - Facter::Util::EC2.stubs(:has_euca_mac?).returns(false) - Facter::Util::EC2.stubs(:has_ec2_arp?).returns(false) + it "is unsuitable if ec2 endpoint is not reachable" do + Facter.fact(:virtual).stubs(:value).returns "xen" + querier.stubs(:reachable?).returns false + expect(subject).to_not be_suitable + end - # Assume we can connect - Facter::Util::EC2.stubs(:can_connect?).returns(true) + describe "when the ec2 endpoint is reachable" do + before do + querier.stubs(:reachable?).returns true end - it "should create ec2_user_data fact" do - # No meta-data - Object.any_instance.expects(:open).\ - with("#{api_prefix}/2008-02-01/meta-data/").\ - at_least_once.returns(StringIO.new("")) - - Facter::Util::EC2.stubs(:read_uri). - with("#{api_prefix}/latest/user-data/"). - returns("test") - - # Force a fact load - Facter.collection.internal_loader.load(:ec2) - - Facter.fact(:ec2_userdata).value.should == ["test"] + it "is suitable if the virtual fact is xen" do + Facter.fact(:virtual).stubs(:value).returns "xen" + expect(subject).to be_suitable end - it "should return nil if open fails" do - Facter.stubs(:warn) # do not pollute test output - Facter.expects(:warn).with('Could not retrieve ec2 metadata: host unreachable') - - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/"). - at_least_once.raises(RuntimeError, 'host unreachable') - - Facter::Util::EC2.stubs(:read_uri). - with("#{api_prefix}/latest/user-data/"). - raises(RuntimeError, 'host unreachable') - - # Force a fact load - Facter.collection.internal_loader.load(:ec2) - - Facter.fact(:ec2_userdata).value.should be_nil + it "is suitable if the virtual fact is xenu" do + Facter.fact(:virtual).stubs(:value).returns "xenu" + expect(subject).to be_suitable end + end + it "resolves the value by fetching the rest endpoint" do + querier.expects(:fetch).returns "user data!" + expect(subject.value).to eq "user data!" end +end - describe "when api connect test fails" do - before :each do - Facter.stubs(:warnonce) - end +describe "flattened versions of ec2 facts" do + # These facts are tricky to test because they are dynamic facts, and they are + # generated from a fact that is defined in the same file. In order to pull + # this off we need to define the ec2_metadata fact ahead of time so that we + # can stub the value, and then manually load the correct files. - it "should not populate ec2_userdata" do - # Emulate ec2 for now as it matters little to this test - Facter::Util::EC2.stubs(:has_euca_mac?).returns(true) - Facter::Util::EC2.stubs(:has_ec2_arp?).never - Facter::Util::EC2.expects(:can_connect?).at_least_once.returns(false) + it "unpacks the ec2_metadata fact" do + Facter.define_fact(:ec2_metadata).stubs(:value).returns({"hello" => "world"}) + Facter.collection.internal_loader.load(:ec2) - # The API should never be called at this point - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/meta-data/").never - Object.any_instance.expects(:open). - with("#{api_prefix}/2008-02-01/user-data/").never + expect(Facter.value("ec2_hello")).to eq "world" + end - # Force a fact load - Facter.collection.internal_loader.load(:ec2) + it "does not set any flat ec2 facts if the ec2_metadata fact is nil" do + Facter.define_fact(:ec2_metadata).stubs(:value) + Facter.define_fact(:ec2_userdata).stubs(:value).returns(nil) - Facter.fact(:ec2_userdata).should == nil - end + Facter.collection.internal_loader.load(:ec2) - it "should rescue the exception" do - Facter::Util::EC2.expects(:open).with("#{api_prefix}:80/").raises(Timeout::Error) + all_facts = Facter.collection.to_hash - Facter::Util::EC2.should_not be_can_connect - end + ec2_facts = all_facts.keys.select { |k| k =~ /^ec2_/ } + expect(ec2_facts).to be_empty end + end diff --git a/spec/unit/util/ec2_spec.rb b/spec/unit/util/ec2_spec.rb index f963db6c32..7af59d8334 100755 --- a/spec/unit/util/ec2_spec.rb +++ b/spec/unit/util/ec2_spec.rb @@ -4,6 +4,10 @@ require 'facter/util/ec2' describe Facter::Util::EC2 do + before do + # Squelch deprecation notices + Facter.stubs(:warnonce) + end # This is the standard prefix for making an API call in EC2 (or fake) # environments. let(:api_prefix) { "http://169.254.169.254" } diff --git a/spec/unit/util/values_spec.rb b/spec/unit/util/values_spec.rb index 557eb195e6..bc347c49a6 100644 --- a/spec/unit/util/values_spec.rb +++ b/spec/unit/util/values_spec.rb @@ -128,4 +128,44 @@ end end end + + describe "flatten_structure" do + it "converts a string to a hash containing that string" do + input = "foo" + output = described_class.flatten_structure("path", input) + expect(output).to eq({"path" => "foo"}) + end + + it "converts an array to a hash with the array elements with indexes" do + input = ["foo"] + output = described_class.flatten_structure("path", input) + expect(output).to eq({"path_0" => "foo"}) + end + + it "prefixes a non-nested hash with the given path" do + input = {"foo" => "bar"} + output = described_class.flatten_structure("path", input) + expect(output).to eq({"path_foo" => "bar"}) + end + + it "flattens elements till it reaches the first non-flattenable structure" do + input = { + "first" => "second", + "arr" => ["zero", "one"], + "nested_array" => [ + "hash" => "string", + ], + "top" => {"middle" => ['bottom']}, + } + output = described_class.flatten_structure("path", input) + + expect(output).to eq({ + "path_first" => "second", + "path_arr_0" => "zero", + "path_arr_1" => "one", + "path_nested_array_0_hash" => "string", + "path_top_middle_0" => "bottom" + }) + end + end end