[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