diff options
author | Tim Smith <tsmith@chef.io> | 2020-04-17 10:23:34 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-17 10:23:34 -0700 |
commit | a505760822be517da5efa2caac7cecf43e032147 (patch) | |
tree | 083e122a79f13735cef207157a4c964d4eef3a7a | |
parent | 471ac97407eaf5570da0a25fcec227f727ea5439 (diff) | |
parent | c7569550e5b1bfc0053195c881e98f0facf656d1 (diff) | |
download | ohai-a505760822be517da5efa2caac7cecf43e032147.tar.gz |
Merge pull request #1445 from chef/expand-windows-dmi
Add a plugin for Windows mimicing the Unix dmi plugin
-rw-r--r-- | lib/ohai/common/dmi.rb | 18 | ||||
-rw-r--r-- | lib/ohai/plugins/windows/dmi.rb | 94 | ||||
-rw-r--r-- | lib/ohai/plugins/windows/system_enclosure.rb | 8 | ||||
-rw-r--r-- | spec/unit/plugins/windows/dmi_spec.rb | 179 | ||||
-rw-r--r-- | spec/unit/plugins/windows/system_enclosure_spec.rb | 45 |
5 files changed, 289 insertions, 55 deletions
diff --git a/lib/ohai/common/dmi.rb b/lib/ohai/common/dmi.rb index cc9f4c10..4620a385 100644 --- a/lib/ohai/common/dmi.rb +++ b/lib/ohai/common/dmi.rb @@ -111,6 +111,15 @@ module Ohai id end + SKIPPED_CONVENIENCE_KEYS = %w{ + application_identifier + caption + creation_class_name + size + system_creation_class_name + record_id + }.freeze + # create simplified convenience access keys for each record type # for single occurrences of one type, copy to top level all fields and values # for multiple occurrences of same type, copy to top level all fields and values that are common to all records @@ -122,13 +131,12 @@ module Ohai records[:all_records].each do |record| record.each do |field, value| - next if value.is_a?(Mash) - next if field.to_s == "application_identifier" - next if field.to_s == "size" - next if field.to_s == "record_id" + next unless value.is_a?(String) translated = field.downcase.gsub(/[^a-z0-9]/, "_") - value = value.strip + next if SKIPPED_CONVENIENCE_KEYS.include?(translated.to_s) + + value = value.strip if in_common.key?(translated) in_common[translated] = nil unless in_common[translated] == value else diff --git a/lib/ohai/plugins/windows/dmi.rb b/lib/ohai/plugins/windows/dmi.rb new file mode 100644 index 00000000..6d3637c1 --- /dev/null +++ b/lib/ohai/plugins/windows/dmi.rb @@ -0,0 +1,94 @@ +# +# Author:: Pete Higgins (pete@peterhiggins.org) +# 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. +# + +Ohai.plugin(:DMI) do + provides "dmi" + + # Map the linux component types to their rough Windows API equivalents + DMI_TO_WIN32OLE = { + chassis: "SystemEnclosure", + processor: "Processor", + bios: "Bios", + system: "ComputerSystemProduct", + base_board: "BaseBoard", + }.freeze + + # This regex is in 3 parts for the different supported patterns in camel + # case names coming from the Windows API: + # * Typical camelcase, eg Depth, PartNumber, NumberOfPowerCords + # * Acronyms preceding camelcase, eg SMBIOSAssetTag + # * Acronyms that occur at the end of the name, eg SKU, DeviceID + # + # This cannot handle some property names, eg SMBIOSBIOSVersion. + # https://rubular.com/r/FBNtXod4wkZGAG + SPLIT_REGEX = /[A-Z][a-z0-9]+|[A-Z]{2,}(?=[A-Z][a-z0-9])|[A-Z]{2,}/.freeze + + WINDOWS_TO_UNIX_KEYS = [ + %w{vendor manufacturer}, + %w{identifying_number serial_number}, + %w{name family}, + ].freeze + + collect_data(:windows) do + require "ohai/common/dmi" + require "wmi-lite/wmi" + wmi = WmiLite::Wmi.new + + dmi Mash.new + + # The Windows API returns property names in camel case, eg "SerialNumber", + # while `dmi` returns them as space separated strings, eg "Serial Number". + # `Ohai::Common::DMI.convenience_keys` expects property names in `dmi`'s + # format, so build two parallel hashes with the keys as they come from the + # Windows API and in a faked-out `dmi` version. After the call to + # `Ohai::Common::DMI.convenience_keys` replace the faked-out `dmi` + # collection with the one with the original property names. + DMI_TO_WIN32OLE.each do |dmi_key, ole_key| + wmi_objects = wmi.instances_of("Win32_#{ole_key}").map(&:wmi_ole_object) + + split_name_properties = [] + properties = [] + + wmi_objects.each do |wmi_object| + split_name_properties << Mash.new + properties << Mash.new + + wmi_object.properties_.each do |property| + property_name = property.name + value = wmi_object.invoke(property_name) + + split_name = property_name.scan(SPLIT_REGEX).join(" ") + split_name_properties.last[split_name] = value + properties.last[property_name] = value + end + end + + dmi[dmi_key] = Mash.new(all_records: split_name_properties, _all_records: properties) + end + + Ohai::Common::DMI.convenience_keys(dmi) + + dmi.each_value do |records| + records[:all_records] = records.delete(:_all_records) + + WINDOWS_TO_UNIX_KEYS.each do |windows_key, unix_key| + records[unix_key] = records.delete(windows_key) if records.key?(windows_key) + end + end + end +end diff --git a/lib/ohai/plugins/windows/system_enclosure.rb b/lib/ohai/plugins/windows/system_enclosure.rb index d6ebaf26..edc07d15 100644 --- a/lib/ohai/plugins/windows/system_enclosure.rb +++ b/lib/ohai/plugins/windows/system_enclosure.rb @@ -18,13 +18,11 @@ Ohai.plugin :SystemEnclosure do provides "system_enclosure" + depends "dmi" collect_data(:windows) do - require "wmi-lite/wmi" system_enclosure Mash.new - wmi = WmiLite::Wmi.new - wmi_object = wmi.first_of("Win32_SystemEnclosure").wmi_ole_object - system_enclosure[:manufacturer] = wmi_object.invoke("manufacturer") - system_enclosure[:serialnumber] = wmi_object.invoke("serialnumber") + system_enclosure[:manufacturer] = get_attribute(:dmi, :chassis, :manufacturer) + system_enclosure[:serialnumber] = get_attribute(:dmi, :chassis, :serial_number) end end diff --git a/spec/unit/plugins/windows/dmi_spec.rb b/spec/unit/plugins/windows/dmi_spec.rb new file mode 100644 index 00000000..f9f1c16d --- /dev/null +++ b/spec/unit/plugins/windows/dmi_spec.rb @@ -0,0 +1,179 @@ +# +# Author:: Pete Higgins (pete@peterhiggins.org) +# 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" + +describe Ohai::System, "DMI", :windows_only do + let(:plugin) { get_plugin("windows/dmi") } + + before do + require "wmi-lite/wmi" + + empty_wmi_object = WmiLite::Wmi::Instance.new(double(properties_: [])) + %w{Processor Bios ComputerSystemProduct BaseBoard}.each do |type| + expect_any_instance_of(WmiLite::Wmi).to receive(:instances_of).with("Win32_#{type}").and_return([empty_wmi_object]) + end + end + + context "when property names are different types of camel casing" do + # Each test case has 3 elements: + # * The name of the property as it comes from the Windows APIs + # * The transformed snake-case version of the property name + # * A unique dummy value per test case + CASES = [ + %w{Depth depth aaa}, + %w{PartNumber part_number bbb}, + %w{NumberOfPowerCords number_of_power_cords ccc}, + %w{SKU sku ddd}, + %w{SMBIOSAssetTag smbios_asset_tag eee}, + %w{DeviceID device_id fff}, + %w{L2CacheSize l2_cache_size ggg}, + ].freeze + + before do + properties = CASES.map { |name, _, _| double(name: name) } + wmi_ole_object = double properties_: properties + + CASES.each do |name, _, value| + allow(wmi_ole_object).to receive(:invoke).with(name).and_return(value) + end + + wmi_object = WmiLite::Wmi::Instance.new(wmi_ole_object) + expect_any_instance_of(WmiLite::Wmi).to receive(:instances_of).with("Win32_SystemEnclosure").and_return([wmi_object]) + + plugin.run + end + + CASES.each do |name, transformed_name, value| + it "adds #{name} to :all_records" do + expect(plugin[:dmi][:chassis][:all_records].first[name]).to eq(value) + end + + it "adds #{transformed_name} to the root" do + expect(plugin[:dmi][:chassis][transformed_name]).to eq(value) + end + end + end + + context "when multiple objects of one type are returned from the Windows API" do + before do + properties = [ + double(name: "UniqueProperty"), + double(name: "SharedProperty"), + ] + + wmi_ole_objects = %w{tacos nachos}.map do |value| + object = double properties_: properties + allow(object).to receive(:invoke).with("UniqueProperty").and_return(value) + allow(object).to receive(:invoke).with("SharedProperty").and_return("Taco Bell") + object + end + + wmi_objects = wmi_ole_objects.map { |o| WmiLite::Wmi::Instance.new(o) } + expect_any_instance_of(WmiLite::Wmi).to receive(:instances_of).with("Win32_SystemEnclosure").and_return(wmi_objects) + + plugin.run + end + + it "adds unique values to :all_records" do + values = plugin[:dmi][:chassis][:all_records].map { |r| r["UniqueProperty"] } + expect(values).to eq(%w{tacos nachos}) + end + + it "adds shared values to the root with snake case key" do + expect(plugin[:dmi][:chassis]["shared_property"]).to eq("Taco Bell") + end + end + + context "with extra information that should be filtered out" do + # Each test case has 3 elements: + # * The name of the property as it comes from the Windows APIs + # * The transformed snake-case version of the property name + # * A unique dummy value per test case + FILTERED_KEYS = [ + %w{Caption caption aaa}, + %w{CreationClassName creation_class_name bbb}, + %w{SystemCreationClassName system_creation_class_name ccc}, + ].freeze + + before do + properties = FILTERED_KEYS.map { |name, _, _| double(name: name) } + wmi_ole_object = double properties_: properties + + FILTERED_KEYS.each do |name, _, value| + allow(wmi_ole_object).to receive(:invoke).with(name).and_return(value) + end + + wmi_object = WmiLite::Wmi::Instance.new(wmi_ole_object) + expect_any_instance_of(WmiLite::Wmi).to receive(:instances_of).with("Win32_SystemEnclosure").and_return([wmi_object]) + + plugin.run + end + + FILTERED_KEYS.each do |name, transformed_name, value| + it "adds #{name} to :all_records" do + expect(plugin[:dmi][:chassis][:all_records].first[name]).to eq(value) + end + + it "does not add #{transformed_name} to the root" do + expect(plugin[:dmi][:chassis]).not_to have_key(transformed_name) + end + end + end + + context "with information that should be made to match other platforms" do + # Each test case has 4 elements: + # * The name of the property as it comes from the Windows APIs + # * The transformed snake-case version of the property name + # * The Unix equivalent of the property name + # * A unique dummy value per test case + RENAMED_KEYS = [ + %w{Vendor vendor manufacturer aaa}, + %w{IdentifyingNumber identifying_number serial_number bbb}, + %w{Name name family ccc}, + ].freeze + + before do + properties = RENAMED_KEYS.map { |name, _, _, _| double(name: name) } + wmi_ole_object = double properties_: properties + + RENAMED_KEYS.each do |name, _, _, value| + allow(wmi_ole_object).to receive(:invoke).with(name).and_return(value) + end + + wmi_object = WmiLite::Wmi::Instance.new(wmi_ole_object) + expect_any_instance_of(WmiLite::Wmi).to receive(:instances_of).with("Win32_SystemEnclosure").and_return([wmi_object]) + + plugin.run + end + + RENAMED_KEYS.each do |name, transformed_name, renamed_name, value| + it "adds #{name} to :all_records" do + expect(plugin[:dmi][:chassis][:all_records].first[name]).to eq(value) + end + + it "adds #{renamed_name} to the root" do + expect(plugin[:dmi][:chassis][renamed_name]).to eq(value) + end + + it "does not add #{transformed_name} to the root" do + expect(plugin[:dmi][:chassis]).not_to have_key(transformed_name) + end + end + end +end diff --git a/spec/unit/plugins/windows/system_enclosure_spec.rb b/spec/unit/plugins/windows/system_enclosure_spec.rb deleted file mode 100644 index 0e1bbc93..00000000 --- a/spec/unit/plugins/windows/system_enclosure_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -# -# Author:: Stuart Preston (<stuart@chef.io>) -# Copyright:: Copyright (c) 2018, 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" - -describe Ohai::System, "System Enclosure", :windows_only do - before do - require "wmi-lite/wmi" - @plugin = get_plugin("windows/system_enclosure") - manufacturer = double("WIN32OLE", name: "manufacturer", value: "My Fake Manufacturer") - serialnumber = double("WIN32OLE", name: "serialnumber", value: "1234123412341234") - property_map = [ manufacturer, serialnumber ] - - wmi_ole_object = double( "WIN32OLE", properties_: property_map) - allow(wmi_ole_object).to receive(:invoke).with(manufacturer.name).and_return(manufacturer.value) - allow(wmi_ole_object).to receive(:invoke).with(serialnumber.name).and_return(serialnumber.value) - wmi_object = WmiLite::Wmi::Instance.new(wmi_ole_object) - expect_any_instance_of(WmiLite::Wmi).to receive(:first_of).with(("Win32_SystemEnclosure")).and_return(wmi_object) - end - - it "returns the manufacturer" do - @plugin.run - expect(@plugin["system_enclosure"]["manufacturer"]).to eql("My Fake Manufacturer") - end - - it "returns a serial number" do - @plugin.run - expect(@plugin["system_enclosure"]["serialnumber"]).to eql("1234123412341234") - end -end |