summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2020-04-17 10:23:34 -0700
committerGitHub <noreply@github.com>2020-04-17 10:23:34 -0700
commita505760822be517da5efa2caac7cecf43e032147 (patch)
tree083e122a79f13735cef207157a4c964d4eef3a7a
parent471ac97407eaf5570da0a25fcec227f727ea5439 (diff)
parentc7569550e5b1bfc0053195c881e98f0facf656d1 (diff)
downloadohai-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.rb18
-rw-r--r--lib/ohai/plugins/windows/dmi.rb94
-rw-r--r--lib/ohai/plugins/windows/system_enclosure.rb8
-rw-r--r--spec/unit/plugins/windows/dmi_spec.rb179
-rw-r--r--spec/unit/plugins/windows/system_enclosure_spec.rb45
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