summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith84@gmail.com>2020-01-29 16:54:50 -0800
committerTim Smith <tsmith84@gmail.com>2020-03-02 12:42:53 -0800
commit808ce32e95c9e4799d1dabbbdb155d6484e31b28 (patch)
treee9a644287351830741d091fa46224c913a90c801
parentc5bfea7486beb649091d9248310252ae2c0ab416 (diff)
downloadohai-new_azure_versions.tar.gz
Support fetching the latest version of Azure metadatanew_azure_versions
This is inspired by the way we do this in EC2, but it's different in many ways since Azure presents the latest version in a different way. Signed-off-by: Tim Smith <tsmith@chef.io>
-rw-r--r--lib/ohai/mixin/azure_metadata.rb87
-rw-r--r--spec/unit/mixin/azure_metadata_spec.rb46
2 files changed, 116 insertions, 17 deletions
diff --git a/lib/ohai/mixin/azure_metadata.rb b/lib/ohai/mixin/azure_metadata.rb
index 5e924b4b..903c3381 100644
--- a/lib/ohai/mixin/azure_metadata.rb
+++ b/lib/ohai/mixin/azure_metadata.rb
@@ -1,6 +1,6 @@
#
# Author:: Tim Smith (<tsmith@chef.io>)
-# Copyright:: Copyright 2017 Chef Software, Inc.
+# Copyright:: 2017-2020 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,30 +19,87 @@ require "net/http" unless defined?(Net::HTTP)
module Ohai
module Mixin
+ #
+ # This code parses the Azure Instance Metadata API to provide details
+ # of the running instance.
+ #
+ # The code probes the instance metadata endpoint for
+ # available versions, determines the most advanced version known to
+ # work and executes the metadata retrieval using that version.
+ #
+ # If no compatible version is found, an empty hash is returned.
+ #
module AzureMetadata
AZURE_METADATA_ADDR ||= "169.254.169.254".freeze
- AZURE_METADATA_URL ||= "/metadata/instance?api-version=2017-08-01".freeze
- # fetch the meta content with a timeout and the required header
+ # it's important that the newer versions are at the end of this array so we can skip sorting it
+ AZURE_SUPPORTED_VERSIONS ||= %w{ 2017-04-02 2017-08-01 2017-12-01 2018-02-01 2018-04-02
+ 2018-10-01 2019-02-01 2019-03-11 2019-04-30 2019-06-01
+ 2019-06-04 2019-08-01 2019-08-15 2019-11-01 }.freeze
+
+ def best_api_version
+ @api_version ||= begin
+ logger.trace("Mixin AzureMetadata: Fetching http://#{AZURE_METADATA_ADDR}/metadata/instance to determine the latest supported metadata release")
+ response = http_get("/metadata/instance")
+ if response.code == "404"
+ logger.trace("Mixin AzureMetadata: Received HTTP 404 from metadata server while determining API version, assuming #{AZURE_SUPPORTED_VERSIONS[-1]}")
+ return AZURE_SUPPORTED_VERSIONS.last
+ elsif response.code != "400" # 400 is actually what we want
+ raise "Mixin AzureMetadata: Unable to determine Azure metadata version (returned #{response.code} response)"
+ end
+
+ # azure returns a list of the 3 latest versions it supports
+ versions = parse_json(response.body)["newest-versions"]
+ versions.sort!
+
+ until versions.empty? || AZURE_SUPPORTED_VERSIONS.include?(versions.last)
+ pv = versions.pop
+ logger.trace("Mixin AzureMetadata: Azure metadata version #{pv} is not present in the versions provided by the Azure Instance Metadata service")
+ end
+
+ if versions.empty?
+ logger.debug "Mixin AzureMetadata: The short list of supported versions provided by Azure Instance Metadata service doesn't match any known versions to Ohai. Using the latest supported release known to Ohai instead: #{AZURE_SUPPORTED_VERSIONS.last}"
+ return AZURE_SUPPORTED_VERSIONS.last
+ end
+
+ logger.trace("Mixin AzureMetadata: Latest supported Azure metadata version: #{versions.last}")
+ versions.last
+ end
+ end
+
+ # fetch the meta content with a timeout and the required header and a read timeout of 6s
+ #
+ # @param [String] the relative uri to fetch from the Azure Metadata Service URL
+ #
+ # @return [Net::HTTP]
def http_get(uri)
+ full_uri = URI.join("http://#{AZURE_METADATA_ADDR}", uri)
conn = Net::HTTP.start(AZURE_METADATA_ADDR)
conn.read_timeout = 6
- conn.get(uri, { "Metadata" => "true" })
+ conn.get(full_uri, { "Metadata" => "true" })
end
- def fetch_metadata
- logger.trace("Mixin AzureMetadata: Fetching metadata from host #{AZURE_METADATA_ADDR} at #{AZURE_METADATA_URL}")
- response = http_get(AZURE_METADATA_URL)
+ # parse JSON data from a String to a Hash
+ #
+ # @param [String] response_body json as string to parse
+ #
+ # @return [Hash]
+ def parse_json(response_body)
+ data = StringIO.new(response_body)
+ parser = FFI_Yajl::Parser.new
+ parser.parse(data)
+ rescue FFI_Yajl::ParseError
+ logger.warn("Mixin AzureMetadata: Metadata response is NOT valid JSON")
+ nil
+ end
+
+ def fetch_metadata(api_version = nil)
+ metadata_url = "/metadata/instance?api-version=#{best_api_version}"
+ logger.trace("Mixin AzureMetadata: Fetching metadata from host #{AZURE_METADATA_ADDR} at #{metadata_url}")
+ response = http_get(metadata_url)
if response.code == "200"
- begin
- data = StringIO.new(response.body)
- parser = FFI_Yajl::Parser.new
- parser.parse(data)
- rescue FFI_Yajl::ParseError
- logger.warn("Mixin AzureMetadata: Metadata response is NOT valid JSON")
- nil
- end
+ parse_json(response.body)
else
logger.warn("Mixin AzureMetadata: Received response code #{response.code} requesting metadata")
nil
diff --git a/spec/unit/mixin/azure_metadata_spec.rb b/spec/unit/mixin/azure_metadata_spec.rb
index 50f6ecd0..6ae6e3b6 100644
--- a/spec/unit/mixin/azure_metadata_spec.rb
+++ b/spec/unit/mixin/azure_metadata_spec.rb
@@ -1,6 +1,6 @@
#
# Author:: Tim Smith <tsmith@chef.io>
-# Copyright:: 2017 Chef Software, Inc.
+# Copyright:: 2017-2020 Chef Software, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -36,12 +36,54 @@ describe Ohai::Mixin::AzureMetadata do
allow(http_mock).to receive(:read_timeout=)
allow(Net::HTTP).to receive(:start).with("169.254.169.254").and_return(http_mock)
- expect(http_mock).to receive(:get).with("http://www.chef.io", { "Metadata" => "true" })
+ expect(http_mock).to receive(:get).with(URI("http://www.chef.io"), { "Metadata" => "true" })
mixin.http_get("http://www.chef.io")
end
end
+ describe "#best_api_version" do
+ before do
+ allow(mixin).to receive(:http_get).and_return(response)
+ end
+
+ context "when azure returns versions we know about" do
+ let(:response) { double("Net::HTTP Response", body: "{\"error\":\"Bad request. api-version was not specified in the request. For more information refer to aka.ms/azureimds\",\"newest-versions\":[\"2019-08-15\",\"2019-08-01\",\"2019-07-15\"]}", code: "400") }
+
+ it "returns the most recent version" do
+ expect(mixin.best_api_version).to eq("2019-08-15")
+ end
+ end
+
+ context "when azure doesn't return any versions we know about" do
+ let(:response) { double("Net::HTTP Response", body: "{\"error\":\"Bad request. api-version was not specified in the request. For more information refer to aka.ms/azureimds\",\"newest-versions\":[\"2021-01-02\",\"2020-08-01\",\"2020-07-15\"]}", code: "400") }
+
+ it "returns the most recent version we know of" do
+ expect(mixin.best_api_version).to eq("2019-11-01")
+ end
+ end
+
+ context "when the response code is 404" do
+ let(:response) { double("Net::HTTP Response", code: "404") }
+
+ it "returns the most recent version we know of" do
+ expect(mixin.best_api_version).to eq("2019-11-01")
+ end
+ end
+
+ context "when the response code is unexpected" do
+ let(:response) { double("Net::HTTP Response", body: "{\"error\":\"Bad request. api-version was not specified in the request. For more information refer to aka.ms/azureimds\",\"newest-versions\":[\"2021-01-02\",\"2020-08-01\",\"2020-07-15\"]}", code: "418") }
+
+ it "raises an error" do
+ expect { mixin.best_api_version }.to raise_error(RuntimeError)
+ end
+ end
+ end
+
describe "#fetch_metadata" do
+ before do
+ allow(mixin).to receive(:best_api_version).and_return("2019-11-01")
+ end
+
it "returns an empty hash given a non-200 response" do
http_mock = double("http", { code: "500" })
allow(mixin).to receive(:http_get).and_return(http_mock)