diff options
-rw-r--r-- | .github/workflows/exec.yml | 2 | ||||
-rw-r--r-- | lib/ohai/mixin/oci_metadata.rb | 69 | ||||
-rw-r--r-- | lib/ohai/plugins/cloud.rb | 22 | ||||
-rw-r--r-- | lib/ohai/plugins/oci.rb | 94 | ||||
-rw-r--r-- | spec/unit/mixin/oci_metadata_spec.rb | 66 | ||||
-rw-r--r-- | spec/unit/plugins/cloud_spec.rb | 38 | ||||
-rw-r--r-- | spec/unit/plugins/oci_spec.rb | 198 |
7 files changed, 488 insertions, 1 deletions
diff --git a/.github/workflows/exec.yml b/.github/workflows/exec.yml index 13dca0ca..ba734d5f 100644 --- a/.github/workflows/exec.yml +++ b/.github/workflows/exec.yml @@ -6,7 +6,7 @@ name: exec push: branches: - main - - 1=7-stable + - 17-stable - 16-stable permissions: diff --git a/lib/ohai/mixin/oci_metadata.rb b/lib/ohai/mixin/oci_metadata.rb new file mode 100644 index 00000000..b04243a3 --- /dev/null +++ b/lib/ohai/mixin/oci_metadata.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +# +# Author:: Renato Covarrubias (<rnt@rnt.cl>) +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require "net/http" unless defined?(Net::HTTP) + +module Ohai + module Mixin + module OCIMetadata + OCI_METADATA_ADDR = "169.254.169.254" + OCI_METADATA_URL = "/opc/v2" + CHASSIS_ASSET_TAG_FILE = "/sys/devices/virtual/dmi/id/chassis_asset_tag" + + # fetch the meta content with a timeout and the required header + def http_get(uri) + conn = Net::HTTP.start(OCI_METADATA_ADDR) + conn.read_timeout = 6 + conn.get( + uri, + { + "Authorization" => "Bearer Oracle", + "User-Agent" => "chef-ohai/#{Ohai::VERSION}", + } + ) + end + + # 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 = String(response_body) + parser = FFI_Yajl::Parser.new + parser.parse(data) + rescue FFI_Yajl::ParseError + logger.warn("Mixin OciMetadata: Metadata response is NOT valid JSON") + nil + end + + # Fetch metadata from api + def fetch_metadata(metadata = "instance") + response = http_get("#{OCI_METADATA_URL}/#{metadata}") + return nil unless response.code == "200" + + if response.code == "200" + parse_json(response.body) + else + logger.warn("Mixin OciMetadata: Received response code #{response.code} requesting metadata") + nil + end + end + end + end +end diff --git a/lib/ohai/plugins/cloud.rb b/lib/ohai/plugins/cloud.rb index 8e79a824..503516ab 100644 --- a/lib/ohai/plugins/cloud.rb +++ b/lib/ohai/plugins/cloud.rb @@ -28,6 +28,7 @@ Ohai.plugin(:Cloud) do depends "azure" depends "digital_ocean" depends "softlayer" + depends "oci" # Class to help enforce the interface exposed to node[:cloud] (OHAI-542) # @@ -336,6 +337,26 @@ Ohai.plugin(:Cloud) do @cloud_attr_obj.provider = "softlayer" end + # ---------------------------------------- + # OCI + # ---------------------------------------- + + # Is current Oracle Cloud Infrastructure? + # + # === Return + # true:: If oci Hash is defined + # false:: Otherwise + def on_oci? + oci != nil + end + + # Fill cloud hash with OCI values + def oci_values + oci["metadata"]["network"]["interface"].each { |vnic| @cloud_attr_obj.add_ipv4_addr(vnic["privateIp"], :private) } + @cloud_attr_obj.local_hostname = oci["metadata"]["compute"]["hostname"] + @cloud_attr_obj.provider = "oci" + end + collect_data do require "ipaddr" unless defined?(IPAddr) @@ -351,6 +372,7 @@ Ohai.plugin(:Cloud) do get_digital_ocean_values if on_digital_ocean? get_softlayer_values if on_softlayer? get_alibaba_values if on_alibaba? + oci_values if on_oci? cloud @cloud_attr_obj.cloud_mash end diff --git a/lib/ohai/plugins/oci.rb b/lib/ohai/plugins/oci.rb new file mode 100644 index 00000000..04e83ba5 --- /dev/null +++ b/lib/ohai/plugins/oci.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +# +# Author:: Renato Covarrubias (<rnt@rnt.cl>) +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +Ohai.plugin(:Oci) do + require_relative "../mixin/oci_metadata" + require_relative "../mixin/http_helper" + + include Ohai::Mixin::OCIMetadata + include Ohai::Mixin::HttpHelper + + provides "oci" + + collect_data do + oci_metadata_from_hints = hint?("oci") + if oci_metadata_from_hints + logger.trace("Plugin OCI: oci hint is present. Parsing any hint data.") + oci Mash.new + oci_metadata_from_hints.each { |k, v| oci[k] = v } + oci["metadata"] = parse_metadata + elsif oci_chassis_asset_tag? + logger.trace("Plugin oci: No hints present, but system appears to be on oci.") + oci Mash.new + oci["metadata"] = parse_metadata + else + logger.trace("Plugin oci: No hints present and doesn't appear to be on oci.") + false + end + end + + def oci_chassis_asset_tag? + has_oci_chassis_asset_tag = false + if file_exist?(Ohai::Mixin::OCIMetadata::CHASSIS_ASSET_TAG_FILE) + file_open(Ohai::Mixin::OCIMetadata::CHASSIS_ASSET_TAG_FILE).each do |line| + next unless /OracleCloud.com/.match?(line) + + logger.trace("Plugin oci: Found OracleCloud.com chassis_asset_tag used by oci.") + has_oci_chassis_asset_tag = true + break + end + end + has_oci_chassis_asset_tag + end + + def parse_metadata + return nil unless can_socket_connect?(Ohai::Mixin::OCIMetadata::OCI_METADATA_ADDR, 80) + + instance_data = fetch_metadata("instance") + return nil if instance_data.nil? + + metadata = Mash.new + metadata["compute"] = Mash.new + + instance_data.each do |k, v| + metadata["compute"][k] = v + end + + vnics_data = fetch_metadata("vnics") + + unless vnics_data.nil? + metadata["network"] = Mash.new + metadata["network"]["interface"] = [] + vnics_data.each do |v| + metadata["network"]["interface"].append(v) + end + end + + volume_attachments_data = fetch_metadata("volumeAttachments") + + unless volume_attachments_data.nil? + metadata["volumes"] = Mash.new + volume_attachments_data.each do |k, v| + metadata["volumes"][k] = v + end + end + + metadata + end +end diff --git a/spec/unit/mixin/oci_metadata_spec.rb b/spec/unit/mixin/oci_metadata_spec.rb new file mode 100644 index 00000000..3135b028 --- /dev/null +++ b/spec/unit/mixin/oci_metadata_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +# +# Author:: Renato Covarrubias <rnt@rnt.cl> +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDIT"Net::HTTP Response"NS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "spec_helper" +require "ohai/mixin/oci_metadata" + +describe Ohai::Mixin::OCIMetadata do + let(:mixin) do + mixin = Object.new.extend(Ohai::Mixin::OCIMetadata) + mixin + end + + before do + logger = instance_double("Mixlib::Log::Child", trace: nil, debug: nil, warn: nil) + allow(mixin).to receive(:logger).and_return(logger) + end + + describe "#http_get" do + it "gets the passed URI" do + http_mock = double("http") + allow(http_mock).to receive(:read_timeout=) + allow(Net::HTTP).to receive(:start).with(Ohai::Mixin::OCIMetadata::OCI_METADATA_ADDR).and_return(http_mock) + + expect(http_mock).to receive(:get).with(Ohai::Mixin::OCIMetadata::OCI_METADATA_ADDR, + { "Authorization" => "Bearer Oracle", + "User-Agent" => "chef-ohai/#{Ohai::VERSION}" }) + mixin.http_get(Ohai::Mixin::OCIMetadata::OCI_METADATA_ADDR) + end + end + + describe "#fetch_metadata" do + it "returns an empty hash given a non-200 response" do + http_mock = double("http", { code: "404" }) + allow(mixin).to receive(:http_get).and_return(http_mock) + + expect(mixin.logger).not_to receive(:warn) + vals = mixin.fetch_metadata + expect(vals).to eq(nil) + end + + it "returns a populated hash given valid JSON response" do + http_mock = double("http", { code: "200", body: '{ "foo": "bar"}' }) + allow(mixin).to receive(:http_get).and_return(http_mock) + + expect(mixin.logger).not_to receive(:warn) + vals = mixin.fetch_metadata + expect(vals).to eq({ "foo" => "bar" }) + end + end +end diff --git a/spec/unit/plugins/cloud_spec.rb b/spec/unit/plugins/cloud_spec.rb index 95d210e0..82dee74f 100644 --- a/spec/unit/plugins/cloud_spec.rb +++ b/spec/unit/plugins/cloud_spec.rb @@ -87,6 +87,7 @@ describe Ohai::System, "plugin cloud" do @plugin[:gce] = nil @plugin[:digital_ocean] = nil @plugin[:softlayer] = nil + @plugin[:oci] = nil @plugin.run expect(@plugin[:cloud]).to be_nil end @@ -511,4 +512,41 @@ describe Ohai::System, "plugin cloud" do end end + describe "with OCI mash" do + before do + @plugin[:oci] = Mash.new + @plugin[:oci][:metadata] = { + "compute" => { + "hostname" => "my-hostname", + }, + "network" => { + "interface" => [ + { "vnicId" => "ocid1.vnic.oc1.phx.exampleuniqueID", "privateIp" => "10.0.3.6", "vlanTag" => 11, + "macAddr" => "00:00:00:00:00:01", "virtualRouterIp" => "10.0.3.1", "subnetCidrBlock" => "10.0.3.0/24", + "nicIndex" => 0 }, + ], + }, + } + end + + it "doesn't populates cloud vm_name" do + @plugin.run + expect(@plugin[:cloud][:vm_name]).not_to eq("testtest") + end + + it "populates cloud local_hostname" do + @plugin.run + expect(@plugin[:cloud][:local_hostname]).to eq("my-hostname") + end + + it "populates cloud private ip" do + @plugin.run + expect(@plugin[:cloud][:local_ipv4]).to eq(@plugin[:oci][:metadata][:network][:interface][0]["privateIp"]) + end + + it "populates cloud provider" do + @plugin.run + expect(@plugin[:cloud][:provider]).to eq("oci") + end + end end diff --git a/spec/unit/plugins/oci_spec.rb b/spec/unit/plugins/oci_spec.rb new file mode 100644 index 00000000..53737383 --- /dev/null +++ b/spec/unit/plugins/oci_spec.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true + +# +# Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>) +# Copyright:: Copyright (c) Chef Software Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "spec_helper" +begin + require "win32/registry" unless defined?(Win32::Registry) +rescue LoadError => e + puts "Skipping missing rake dep: #{e}" +end + +describe Ohai::System, "plugin oci" do + let(:plugin) { get_plugin("oci") } + let(:hint) do + { + "local_hostname" => "test-vm", + "provider" => "oci", + } + end + + let(:response_data) do + { + "compute" => { + "availabilityDomain" => "EMIr:PHX-AD-1", + "faultDomain" => "FAULT-DOMAIN-3", + "compartmentId" => "ocid1.tenancy.oc1..exampleuniqueID", + "displayName" => "my-example-instance", + "hostname" => "my-hostname", + "id" => "ocid1.instance.oc1.phx.exampleuniqueID", + "image" => "ocid1.image.oc1.phx.exampleuniqueID", + "metadata" => { + "ssh_authorized_keys" => "example-ssh-key", + }, + "region" => "phx", + "canonicalRegionName" => "us-phoenix-1", + "ociAdName" => "phx-ad-1", + "regionInfo" => { + "realmKey" => "oc1", + "realmDomainComponent" => "oraclecloud.com", + "regionKey" => "PHX", + "regionIdentifier" => "us-phoenix-1", + }, + "shape" => "VM.Standard.E3.Flex", + "state" => "Running", + "timeCreated" => 1_600_381_928_581, + "agentConfig" => { + "monitoringDisabled" => false, + "managementDisabled" => false, + "allPluginsDisabled" => false, + "pluginsConfig" => [ + { "name" => "OS Management Service Agent", "desiredState" => "ENABLED" }, + { "name" => "Custom Logs Monitoring", "desiredState" => "ENABLED" }, + { "name" => "Compute Instance Run Command", "desiredState" => "ENABLED" }, + { "name" => "Compute Instance Monitoring", "desiredState" => "ENABLED" }, + ], + }, + "freeformTags" => { + "Department" => "Finance", + }, + "definedTags" => { + "Operations" => { + "CostCenter" => "42", + }, + }, + }, + "network" => { + "interface" => [ + { "vnicId" => "ocid1.vnic.oc1.phx.exampleuniqueID", "privateIp" => "10.0.3.6", "vlanTag" => 11, + "macAddr" => "00:00:00:00:00:01", "virtualRouterIp" => "10.0.3.1", "subnetCidrBlock" => "10.0.3.0/24", + "nicIndex" => 0 }, + { "vnicId" => "ocid1.vnic.oc1.phx.exampleuniqueID", "privateIp" => "10.0.4.3", "vlanTag" => 12, + "macAddr" => "00:00:00:00:00:02", "virtualRouterIp" => "10.0.4.1", "subnetCidrBlock" => "10.0.4.0/24", + "nicIndex" => 0 }, + ], + }, + } + end + + before do + # skips all the metadata logic unless we want to test it + allow(plugin).to receive(:can_socket_connect?) + .with(Ohai::Mixin::OCIMetadata::OCI_METADATA_ADDR, 80) + .and_return(false) + end + + shared_examples_for "!oci" do + it "does not set the oci attribute" do + plugin.run + expect(plugin[:oci]).to be_nil + end + end + + shared_examples_for "oci" do + it "sets the oci attribute" do + plugin.run + expect(plugin[:oci]).to be_truthy + expect(plugin[:oci]).to have_key(:metadata) + end + end + + describe "with oci hint file" do + before do + allow(plugin).to receive(:hint?).with("oci").and_return(hint) + end + + it "sets the oci cloud attributes" do + plugin.run + expect(plugin[:oci]["provider"]).to eq("oci") + expect(plugin[:oci]["local_hostname"]).to eq("test-vm") + end + end + + describe "without oci hint file not in OCI" do + before do + allow(plugin).to receive(:hint?).with("oci").and_return(false) + allow(plugin).to receive(:file_exist?).with(Ohai::Mixin::OCIMetadata::CHASSIS_ASSET_TAG_FILE).and_return(true) + @double_file = double(Ohai::Mixin::OCIMetadata::CHASSIS_ASSET_TAG_FILE) + allow(@double_file).to receive(:each) + .and_yield("") + allow(plugin).to receive(:file_open).with(Ohai::Mixin::OCIMetadata::CHASSIS_ASSET_TAG_FILE).and_return(@double_file) + end + + it_behaves_like "!oci" + end + + describe "without oci hint file in OCI" do + before do + allow(plugin).to receive(:hint?).with("oci").and_return(false) + allow(plugin).to receive(:file_exist?).with(Ohai::Mixin::OCIMetadata::CHASSIS_ASSET_TAG_FILE).and_return(true) + @double_file = double(Ohai::Mixin::OCIMetadata::CHASSIS_ASSET_TAG_FILE) + allow(@double_file).to receive(:each) + .and_yield("OracleCloud.com") + allow(plugin).to receive(:file_open).with(Ohai::Mixin::OCIMetadata::CHASSIS_ASSET_TAG_FILE).and_return(@double_file) + end + + it_behaves_like "oci" + end + + describe "with non-responsive metadata endpoint" do + before do + allow(plugin).to receive(:hint?).with("oci").and_return({}) + end + + it "does not return metadata information" do + allow(plugin).to receive(:can_socket_connect?) + .with(Ohai::Mixin::OCIMetadata::OCI_METADATA_ADDR, 80) + .and_return(true) + allow(plugin).to receive(:parse_metadata).and_return(nil) + + plugin.run + expect(plugin[:oci]).to have_key(:metadata) + expect(plugin[:oci][:metadata]).to be_nil + end + end + + describe "with responsive metadata endpoint" do + before do + allow(plugin).to receive(:hint?).with("oci").and_return({}) + allow(plugin).to receive(:can_socket_connect?) + .with(Ohai::Mixin::OCIMetadata::OCI_METADATA_ADDR, 80) + .and_return(true) + allow(plugin).to receive(:parse_metadata).and_return(response_data) + plugin.run + end + + it "returns metadata compute information" do + expect(plugin[:oci][:metadata][:compute][:availabilityDomain]).to eq("EMIr:PHX-AD-1") + expect(plugin[:oci][:metadata][:compute][:compartmentId]).to eq("ocid1.tenancy.oc1..exampleuniqueID") + expect(plugin[:oci][:metadata][:compute][:faultDomain]).to eq("FAULT-DOMAIN-3") + expect(plugin[:oci][:metadata][:compute][:hostname]).to eq("my-hostname") + expect(plugin[:oci][:metadata][:compute][:image]).to eq("ocid1.image.oc1.phx.exampleuniqueID") + expect(plugin[:oci][:metadata][:compute][:region]).to eq("phx") + expect(plugin[:oci][:metadata][:compute][:shape]).to eq("VM.Standard.E3.Flex") + expect(plugin[:oci][:metadata][:compute][:state]).to eq("Running") + end + + it "returns metadata network information" do + expect(plugin[:oci][:metadata][:network][:interface][0][:macAddr]).to eq("00:00:00:00:00:01") + expect(plugin[:oci][:metadata][:network][:interface][0][:privateIp]).to eq("10.0.3.6") + end + end +end |