[Pkg-puppet-devel] [facter] 104/180: Revert "Merge branch 'stable' into facter-2"

Stig Sandbeck Mathisen ssm at debian.org
Mon Jun 30 15:06:36 UTC 2014


This is an automated email from the git hooks/post-receive script.

ssm pushed a commit to branch master
in repository facter.

commit 4fad4dece13e08209a95011a6e94381562df59ca
Author: Kylo Ginsberg <kylo at puppetlabs.com>
Date:   Fri May 23 13:17:53 2014 -0700

    Revert "Merge branch 'stable' into facter-2"
    
    This reverts commit 14eef77237ac1c9c0906890af696ad35e0f3b78e, reversing
    changes made to 07629d6a8b4f9c7a5d00cdb87dbc90befd6d1f3f.
---
 lib/facter/core/suitable.rb                |   6 +-
 lib/facter/ec2.rb                          |  59 ++++----
 lib/facter/ec2/rest.rb                     | 129 ++++++++++++++++
 lib/facter/util/ec2.rb                     |   5 +
 lib/facter/util/values.rb                  |  29 ++++
 spec/fixtures/unit/ec2/rest/meta-data/root |  20 +++
 spec/unit/core/suitable_spec.rb            |  10 ++
 spec/unit/ec2/rest_spec.rb                 | 140 +++++++++++++++++
 spec/unit/ec2_spec.rb                      | 234 +++++++++++------------------
 spec/unit/util/ec2_spec.rb                 |   4 +
 spec/unit/util/values_spec.rb              |  40 +++++
 11 files changed, 498 insertions(+), 178 deletions(-)

diff --git a/lib/facter/core/suitable.rb b/lib/facter/core/suitable.rb
index 1b3c04f..0262470 100644
--- a/lib/facter/core/suitable.rb
+++ b/lib/facter/core/suitable.rb
@@ -108,10 +108,6 @@ module Facter::Core::Suitable
   #
   # @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 09e0109..2e57592 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 0000000..e9c1fca
--- /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 '<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
diff --git a/lib/facter/util/ec2.rb b/lib/facter/util/ec2.rb
index c6e5dca..b81c8fe 100644
--- a/lib/facter/util/ec2.rb
+++ b/lib/facter/util/ec2.rb
@@ -11,6 +11,7 @@ module Facter::Util::EC2
     # 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 @@ module Facter::Util::EC2
     # 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 @@ module Facter::Util::EC2
     # 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 @@ module Facter::Util::EC2
   #
   # @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 a7048d5..1fbb1b6 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 @@ module Facter
         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 0000000..9ec3bbe
--- /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 4c0b1fd..8277408 100644
--- a/spec/unit/core/suitable_spec.rb
+++ b/spec/unit/core/suitable_spec.rb
@@ -92,5 +92,15 @@ describe Facter::Core::Suitable do
 
       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 0000000..5c74b49
--- /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 at 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 f26a613..28ad20a 100755
--- a/spec/unit/ec2_spec.rb
+++ b/spec/unit/ec2_spec.rb
@@ -1,187 +1,127 @@
-#! /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
+require 'facter/ec2/rest'
 
-    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"))
+describe "ec2_metadata" do
+  let(:querier) { stub('EC2 metadata querier') }
 
-      Object.any_instance.expects(:open).
-        with("#{api_prefix}/2008-02-01/meta-data/foo").
-        at_least_once.returns(StringIO.new("bar"))
+  before do
+    Facter::EC2::Metadata.stubs(:new).returns querier
 
-      Facter.collection.internal_loader.load(:ec2)
-
-      Facter.fact(:ec2_foo).value.should == "bar"
-    end
+    # Prevent flattened facts from forcing evaluation of the ec2 metadata fact
+    Facter.stubs(:value).with(:ec2_metadata)
+    Facter.collection.internal_loader.load(:ec2)
+    Facter.unstub(:value)
+  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
+    querier.stubs(:reachable?).returns false
+    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)
+  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
 
-      # Assume we can connect
-      Facter::Util::EC2.stubs(:can_connect?).returns(true)
-    end
+describe "ec2_userdata" do
+  let(:querier) { stub('EC2 metadata querier') }
 
-    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(""))
+  before do
+    Facter::EC2::Userdata.stubs(:new).returns querier
 
-      Facter::Util::EC2.stubs(:read_uri).
-        with("#{api_prefix}/latest/user-data/").
-        returns("test")
+    # Prevent flattened facts from forcing evaluation of the ec2 metadata fact
+    Facter.stubs(:value).with(:ec2_metadata)
+    Facter.collection.internal_loader.load(:ec2)
+    Facter.unstub(:value)
+  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
+    querier.stubs(:reachable?).returns(true)
+    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 f963db6..7af59d8 100755
--- a/spec/unit/util/ec2_spec.rb
+++ b/spec/unit/util/ec2_spec.rb
@@ -4,6 +4,10 @@ require 'spec_helper'
 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 557eb19..bc347c4 100644
--- a/spec/unit/util/values_spec.rb
+++ b/spec/unit/util/values_spec.rb
@@ -128,4 +128,44 @@ describe Facter::Util::Values do
       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

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-puppet/facter.git



More information about the Pkg-puppet-devel mailing list