[Pkg-puppet-devel] [facter] 149/180: (FACT-335) Add GCE metadata facts

Stig Sandbeck Mathisen ssm at debian.org
Mon Jun 30 15:06:42 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 a776efe38f57baf9e2d87fa104940ccbb165e9fb
Author: Adrien Thebo <git at somethingsinistral.net>
Date:   Wed Jun 18 19:17:04 2014 -0700

    (FACT-335) Add GCE metadata facts
    
    Implementation derived from d7c863eb.
---
 lib/facter/gce.rb                             | 16 +++++
 lib/facter/gce/metadata.rb                    | 87 +++++++++++++++++++++++++++
 spec/fixtures/unit/gce/metadata/metadata.json | 69 +++++++++++++++++++++
 spec/unit/gce/metadata_spec.rb                | 49 +++++++++++++++
 spec/unit/gce_spec.rb                         | 34 +++++++++++
 5 files changed, 255 insertions(+)

diff --git a/lib/facter/gce.rb b/lib/facter/gce.rb
new file mode 100644
index 0000000..ef94104
--- /dev/null
+++ b/lib/facter/gce.rb
@@ -0,0 +1,16 @@
+require 'facter/gce/metadata'
+
+Facter.define_fact(:gce) do
+  define_resolution(:rest) do
+    confine :virtual => 'gce'
+
+    confine do
+      Facter.json?
+    end
+
+    setcode do
+      querier = Facter::GCE::Metadata.new
+      querier.fetch
+    end
+  end
+end
diff --git a/lib/facter/gce/metadata.rb b/lib/facter/gce/metadata.rb
new file mode 100644
index 0000000..63fbbc4
--- /dev/null
+++ b/lib/facter/gce/metadata.rb
@@ -0,0 +1,87 @@
+require 'open-uri'
+
+module Facter
+  module GCE
+
+    # @api private
+    class Metadata
+      CONNECTION_ERRORS = [
+        OpenURI::HTTPError,
+        Errno::EHOSTDOWN,
+        Errno::EHOSTUNREACH,
+        Errno::ENETUNREACH,
+        Errno::ECONNABORTED,
+        Errno::ECONNREFUSED,
+        Errno::ECONNRESET,
+        Errno::ETIMEDOUT,
+        Timeout::Error,
+      ]
+
+      METADATA_URL = "http://metadata/computeMetadata/v1beta1/?recursive=true&alt=json"
+
+      def initialize(url = METADATA_URL)
+        @url = url
+      end
+
+      def fetch
+        with_metadata_server do |body|
+          # This will only be reached if the confine associated with this class
+          # was true which means that JSON was required, but it's a bit
+          # questionable that we're relying on JSON being loaded as a side
+          # effect of that.
+          hash = ::JSON.parse(body)
+          transform_metadata!(hash)
+          hash
+        end
+      end
+
+      private
+
+      def with_metadata_server
+        retry_limit = 3
+        timeout = 0.05
+        body = nil
+        attempts = 0
+
+        begin
+          Timeout.timeout(timeout) do
+            body = open(@url).read
+          end
+        rescue *CONNECTION_ERRORS => e
+          attempts = attempts + 1
+          if attempts < retry_limit
+            retry
+          else
+            Facter.log_exception(e, "Unable to fetch metadata from #{@url}: #{e.message}")
+            return nil
+          end
+        end
+
+        if body
+          yield body
+        end
+      end
+
+      # @return [void]
+      def transform_metadata!(data)
+        case data
+        when Hash
+          data.keys.each do |key|
+            value = data[key]
+            if ["image", "machineType", "zone", "network"].include? key
+              data[key] = value.split('/').last
+            elsif key == "sshKeys"
+              data['sshKeys'] = value.split("\n")
+            end
+            transform_metadata!(value)
+          end
+        when Array
+          data.each do |value|
+            transform_metadata!(value)
+          end
+        end
+        nil
+      end
+    end
+  end
+end
diff --git a/spec/fixtures/unit/gce/metadata/metadata.json b/spec/fixtures/unit/gce/metadata/metadata.json
new file mode 100644
index 0000000..9242708
--- /dev/null
+++ b/spec/fixtures/unit/gce/metadata/metadata.json
@@ -0,0 +1,69 @@
+{
+    "instance": {
+        "attributes": {},
+        "description": "",
+        "disks": [
+            {
+                "deviceName": "gce-facts",
+                "index": 0,
+                "mode": "READ_WRITE",
+                "type": "PERSISTENT"
+            }
+        ],
+        "hostname": "gce-facts.c.dev-sand-box.internal",
+        "id": 18287562037867659655,
+        "image": "projects/485161417235/images/centos6",
+        "machineType": "projects/485161417235/machineTypes/n1-standard-1",
+        "maintenanceEvent": "NONE",
+        "networkInterfaces": [
+            {
+                "accessConfigs": [
+                    {
+                        "externalIp": "23.251.145.218",
+                        "type": "ONE_TO_ONE_NAT"
+                    }
+                ],
+                "forwardedIps": [],
+                "ip": "10.240.193.246",
+                "network": "projects/485161417235/networks/default"
+            }
+        ],
+        "scheduling": {
+            "automaticRestart": "TRUE",
+            "onHostMaintenance": "MIGRATE"
+        },
+        "serviceAccounts": {
+            "485161417235-u737tauareitfc5ju1t5cbmdrasr4giv at developer.gserviceaccount.com": {
+                "aliases": [
+                    "default"
+                ],
+                "email": "485161417235-u737tauareitfc5ju1t5cbmdrasr4giv at developer.gserviceaccount.com",
+                "scopes": [
+                    "https://www.googleapis.com/auth/compute",
+                    "https://www.googleapis.com/auth/devstorage.full_control",
+                    "https://www.googleapis.com/auth/userinfo.email"
+                ]
+            },
+            "default": {
+                "aliases": [
+                    "default"
+                ],
+                "email": "485161417235-u737tauareitfc5ju1t5cbmdrasr4giv at developer.gserviceaccount.com",
+                "scopes": [
+                    "https://www.googleapis.com/auth/compute",
+                    "https://www.googleapis.com/auth/devstorage.full_control",
+                    "https://www.googleapis.com/auth/userinfo.email"
+                ]
+            }
+        },
+        "tags": [],
+        "zone": "projects/485161417235/zones/us-central1-b"
+    },
+    "project": {
+        "attributes": {
+            "sshKeys": "justin:ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKTIyNpvkUHk71q79CfiMOiY9xL9bOazvDfsCLD1UaK+g6kugMRxxiFRIB7aOajOrQl/+ZJ3Cy16f/D6zmSLTMJSIQKiJsGFyCdfkbZ6YKDSKMOpqlZCSeErpJp74X4Olg37DLqkFH2MtuoclrkheY0f87CMTGc/ICr6zDF2DoYM7yCU7y8s6FckcDs6xBUYd+phduQLLuEiUxB95neESvV0ZiD4IoJsEMOj5Y5VLqvmbozlHVu8OqVtUxNj5iIwXMGG/hROvjtZQfFRYn64xhINDt/ozbesh5ah1zn6FosGwfuYM5WJeTsUN53iiXIsDDks2Kv3bLu4fOSQ/P9LEF justin at holguinj.wifi.puppetlabs.net\nadrien:ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB [...]
+        },
+        "numericProjectId": 485161417235,
+        "projectId": "dev-sand-box"
+    }
+}
diff --git a/spec/unit/gce/metadata_spec.rb b/spec/unit/gce/metadata_spec.rb
new file mode 100644
index 0000000..ec19938
--- /dev/null
+++ b/spec/unit/gce/metadata_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+require 'facter/gce/metadata'
+
+describe Facter::GCE::Metadata, :if => Facter.json? do
+
+  describe "contacting the metadata server" do
+    it "retries the request when a connection error is thrown" do
+      subject.stubs(:open).returns(stub(:read => '{"hello": "world"}'))
+      seq = sequence('open-uri seq')
+      Timeout.expects(:timeout).with(0.05).twice.in_sequence(seq).raises(Timeout::Error)
+      Timeout.expects(:timeout).with(0.05).once.in_sequence(seq).yields
+      expect(subject.fetch).to eq({'hello' => 'world'})
+    end
+
+    it "logs the exception when all retries failed" do
+      Timeout.expects(:timeout).with(0.05).times(3).raises(Timeout::Error)
+      Facter.expects(:log_exception).with(instance_of(Timeout::Error), instance_of(String))
+      expect(subject.fetch).to be_nil
+    end
+  end
+
+  describe "parsing the metadata response" do
+    let(:body) { my_fixture_read('metadata.json') }
+    before do
+      subject.stubs(:open).returns(stub(:read => body))
+    end
+
+    it "transforms hash values with the 'image' key" do
+      expect(subject.fetch['instance']['image']).to eq 'centos6'
+    end
+
+    it "transforms hash values with the 'machineType' key" do
+      expect(subject.fetch['instance']['machineType']).to eq 'n1-standard-1'
+    end
+
+    it "transforms hash values with the 'zone' key" do
+      expect(subject.fetch['instance']['zone']).to eq 'us-central1-b'
+    end
+
+    it "transforms hash values with the 'network' key" do
+      expect(subject.fetch['instance']['networkInterfaces'][0]['network']).to eq 'default'
+    end
+
+    it "splits up the elements of the 'sshKeys' value into an array" do
+      expect(subject.fetch['project']['attributes']['sshKeys'][0]).to match(/justin:ssh-rsa/)
+      expect(subject.fetch['project']['attributes']['sshKeys'][1]).to match(/adrien:ssh-rsa/)
+    end
+  end
+end
diff --git a/spec/unit/gce_spec.rb b/spec/unit/gce_spec.rb
new file mode 100644
index 0000000..3676de5
--- /dev/null
+++ b/spec/unit/gce_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+require 'facter/gce/metadata'
+
+describe "gce_metadata" do
+  let(:querier) { stub('GCE metadata querier') }
+
+  before do
+    Facter::GCE::Metadata.stubs(:new).returns querier
+    Facter.collection.internal_loader.load(:ec2)
+  end
+
+  subject { Facter.fact(:gce).resolution(:rest) }
+
+  it "is unsuitable when the virtual type is not gce" do
+    Facter.fact(:virtual).stubs(:value).returns 'kvm'
+    expect(subject).to_not be_suitable
+  end
+
+  it "is unsuitable when JSON is not available" do
+    Facter.stubs(:json?).returns false
+    expect(subject).to_not be_suitable
+  end
+
+  it "is suitable when both the virtual type is gce and JSON is available" do
+    Facter.fact(:virtual).stubs(:value).returns 'gce'
+    Facter.stubs(:json?).returns true
+    expect(subject).to be_suitable
+  end
+
+  it "resolves the fact by querying GCE metadata API" do
+    querier.expects(:fetch).returns({'hello' => 'world'})
+    expect(subject.value).to eq({'hello' => 'world'})
+  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