diff --git a/lib/facts/linux/hypervisors/virtualbox.rb b/lib/facts/linux/hypervisors/virtualbox.rb new file mode 100755 index 0000000000..218b0318d0 --- /dev/null +++ b/lib/facts/linux/hypervisors/virtualbox.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Facts + module Linux + module Hypervisors + class VirtualBox + FACT_NAME = 'hypervisors.virtualbox' + + def call_the_resolver + fact_value = check_virtualbox + Facter::ResolvedFact.new(FACT_NAME, fact_value) + end + + def check_virtualbox + virtualbox_details = nil + + if Facter::Resolvers::Linux::DmiBios.resolve(:product_name) == 'VirtualBox' || + Facter::Resolvers::VirtWhat.resolve(:vm) =~ /virtualbox/ || + Facter::Resolvers::Lspci.resolve(:vm) == 'virtualbox' + + virtualbox_details = {} + + version = Facter::Resolvers::DmiDecode.resolve(:virtualbox_version) + revision = Facter::Resolvers::DmiDecode.resolve(:virtualbox_revision) + + virtualbox_details[:version] = version if version + virtualbox_details[:revision] = revision if revision + end + + virtualbox_details + end + end + end + end +end diff --git a/lib/resolvers/dmi_decode.rb b/lib/resolvers/dmi_decode.rb new file mode 100644 index 0000000000..cba47b8e91 --- /dev/null +++ b/lib/resolvers/dmi_decode.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Facter + module Resolvers + class DmiDecode < BaseResolver + @semaphore = Mutex.new + @fact_list ||= {} + + class << self + private + + def post_resolve(fact_name) + @fact_list.fetch(fact_name) { run_dmidecode(fact_name) } + end + + def run_dmidecode(fact_name) + output = Facter::Core::Execution.execute('dmidecode', logger: log) + + @fact_list[:virtualbox_version] = output.match(/vboxVer_(\S+)/)&.captures&.first + @fact_list[:virtualbox_revision] = output.match(/vboxRev_(\S+)/)&.captures&.first + @fact_list[fact_name] + end + end + end + end +end diff --git a/spec/facter/facts/linux/hypervisors/virtual_box_spec.rb b/spec/facter/facts/linux/hypervisors/virtual_box_spec.rb new file mode 100644 index 0000000000..23110c6b2a --- /dev/null +++ b/spec/facter/facts/linux/hypervisors/virtual_box_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +describe Facts::Linux::Hypervisors::VirtualBox do + describe '#call_the_resolver' do + subject(:fact) { Facts::Linux::Hypervisors::VirtualBox.new } + + let(:version) { '6.4.1' } + let(:revision) { '136177' } + let(:value) { { 'version' => version, 'revision' => revision } } + + before do + allow(Facter::Resolvers::DmiDecode).to receive(:resolve).with(:virtualbox_version).and_return(version) + allow(Facter::Resolvers::DmiDecode).to receive(:resolve).with(:virtualbox_revision).and_return(revision) + allow(Facter::Resolvers::Linux::DmiBios).to receive(:resolve).with(:product_name).and_return('VirtualBox') + end + + it 'calls Facter::Resolvers::Linux::DmiBios' do + fact.call_the_resolver + expect(Facter::Resolvers::Linux::DmiBios).to have_received(:resolve).with(:product_name) + end + + it 'calls Facter::Resolvers::DmiDecode with version' do + fact.call_the_resolver + expect(Facter::Resolvers::DmiDecode).to have_received(:resolve).with(:virtualbox_version) + end + + it 'calls Facter::Resolvers::DmiDecode with revision' do + fact.call_the_resolver + expect(Facter::Resolvers::DmiDecode).to have_received(:resolve).with(:virtualbox_revision) + end + + it 'returns virtualbox fact' do + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'hypervisors.virtualbox', value: value) + end + + context 'when virtualbox is not detected' do + let(:value) { nil } + + before do + allow(Facter::Resolvers::Linux::DmiBios).to receive(:resolve).with(:product_name).and_return('other') + allow(Facter::Resolvers::VirtWhat).to receive(:resolve).with(:vm).and_return('other') + allow(Facter::Resolvers::Lspci).to receive(:resolve).with(:vm).and_return('other') + end + + it 'returns nil' do + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'hypervisors.virtualbox', value: value) + end + end + + context 'when virtualbox details are not present' do + let(:value) { {} } + + before do + allow(Facter::Resolvers::DmiDecode).to receive(:resolve).with(:virtualbox_version).and_return(nil) + allow(Facter::Resolvers::DmiDecode).to receive(:resolve).with(:virtualbox_revision).and_return(nil) + end + + it 'returns virtualbox fact as empty hash' do + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'hypervisors.virtualbox', value: value) + end + end + end +end diff --git a/spec/facter/resolvers/dmi_decode_spec.rb b/spec/facter/resolvers/dmi_decode_spec.rb new file mode 100644 index 0000000000..605ec54399 --- /dev/null +++ b/spec/facter/resolvers/dmi_decode_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +describe Facter::Resolvers::DmiDecode do + describe '#resolve' do + subject(:dmidecode) { Facter::Resolvers::DmiDecode } + + let(:command_output) { load_fixture('dmi_decode').read } + + before do + allow(Facter::Core::Execution).to receive(:execute) + .with('dmidecode', logger: instance_of(Facter::Log)).and_return(command_output) + end + + after { dmidecode.invalidate_cache } + + it 'detects virtualbox version' do + expect(dmidecode.resolve(:virtualbox_version)).to eql('6.1.4') + end + + it 'detects virtualbox revision' do + expect(dmidecode.resolve(:virtualbox_revision)).to eql('136177') + end + + context 'when dmidecode command failed' do + let(:command_output) { 'command not found: dmidecode' } + + it 'detects virtualbox version as nil' do + expect(dmidecode.resolve(:virtualbox_version)).to be(nil) + end + + it 'detects virtualbox revision as nil' do + expect(dmidecode.resolve(:virtualbox_revision)).to be(nil) + end + end + end +end diff --git a/spec/fixtures/dmi_decode b/spec/fixtures/dmi_decode new file mode 100644 index 0000000000..5da3453632 --- /dev/null +++ b/spec/fixtures/dmi_decode @@ -0,0 +1,82 @@ +# dmidecode 3.1 +Getting SMBIOS data from sysfs. +SMBIOS 2.5 present. +10 structures occupying 449 bytes. +Table at 0x000E1000. + +Handle 0x0000, DMI type 0, 20 bytes +BIOS Information + Vendor: innotek GmbH + Version: VirtualBox + Release Date: 12/01/2006 + Address: 0xE0000 + Runtime Size: 128 kB + ROM Size: 128 kB + Characteristics: + ISA is supported + PCI is supported + Boot from CD is supported + Selectable boot is supported + 8042 keyboard services are supported (int 9h) + CGA/mono video services are supported (int 10h) + ACPI is supported + +Handle 0x0001, DMI type 1, 27 bytes +System Information + Manufacturer: innotek GmbH + Product Name: VirtualBox + Version: 1.2 + Serial Number: 0 + UUID: 784198EC-5827-476E-9E24-B0E98E8958F5 + Wake-up Type: Power Switch + SKU Number: Not Specified + Family: Virtual Machine + +Handle 0x0008, DMI type 2, 15 bytes +Base Board Information + Manufacturer: Oracle Corporation + Product Name: VirtualBox + Version: 1.2 + Serial Number: 0 + Asset Tag: Not Specified + Features: + Board is a hosting board + Location In Chassis: Not Specified + Chassis Handle: 0x0003 + Type: Motherboard + Contained Object Handles: 0 + +Handle 0x0003, DMI type 3, 13 bytes +Chassis Information + Manufacturer: Oracle Corporation + Type: Other + Lock: Not Present + Version: Not Specified + Serial Number: Not Specified + Asset Tag: Not Specified + Boot-up State: Safe + Power Supply State: Safe + Thermal State: Safe + Security Status: None + +Handle 0x0007, DMI type 126, 42 bytes +Inactive + +Handle 0x0005, DMI type 126, 15 bytes +Inactive + +Handle 0x0006, DMI type 126, 28 bytes +Inactive + +Handle 0x0002, DMI type 11, 7 bytes +OEM Strings + String 1: vboxVer_6.1.4 + String 2: vboxRev_136177 + +Handle 0x0008, DMI type 128, 8 bytes +OEM-specific Type + Header and Data: + 80 08 08 00 3D A0 2A 00 + +Handle 0xFEFF, DMI type 127, 4 bytes +End Of Table