diff options
author | Tim Smith <tsmith@chef.io> | 2016-03-30 12:29:15 -0700 |
---|---|---|
committer | Tim Smith <tsmith@chef.io> | 2016-03-30 12:29:15 -0700 |
commit | 02defc6ff5ce01204656ae4715c4fdce68843e2e (patch) | |
tree | acd202778ab2c304e3caaf0426ec1a8d09fee3f0 | |
parent | 438fdeba4532c78161e01ade9d23d108ff8bac33 (diff) | |
parent | e6c6d5997cccd24641ae16cfbc73fb5000dc8ad1 (diff) | |
download | ohai-02defc6ff5ce01204656ae4715c4fdce68843e2e.tar.gz |
Merge pull request #778 from sh9189/windows_packages_fix
Windows packages plugin - Get packages from registry
-rw-r--r-- | lib/ohai/plugins/packages.rb | 105 | ||||
-rw-r--r-- | spec/unit/plugins/packages_spec.rb | 356 |
2 files changed, 224 insertions, 237 deletions
diff --git a/lib/ohai/plugins/packages.rb b/lib/ohai/plugins/packages.rb index b9d06451..334db7ce 100644 --- a/lib/ohai/plugins/packages.rb +++ b/lib/ohai/plugins/packages.rb @@ -22,63 +22,78 @@ Ohai.plugin(:Packages) do provides "packages" depends "platform_family" + WINDOWS_ATTRIBUTE_ALIASES = { + "DisplayVersion" => "version", + "Publisher" => "publisher", + "InstallDate" => "installdate" + } + collect_data(:linux) do - if configuration(:enabled) - packages Mash.new - if %w{debian}.include? platform_family - so = shell_out("dpkg-query -W") - pkgs = so.stdout.lines + packages Mash.new + if %w{debian}.include? platform_family + so = shell_out("dpkg-query -W") + pkgs = so.stdout.lines - pkgs.each do |pkg| - name, version = pkg.split - packages[name] = { "version" => version } - end + pkgs.each do |pkg| + name, version = pkg.split + packages[name] = { "version" => version } + end - elsif %w{rhel fedora suse}.include? platform_family - require "shellwords" - format = Shellwords.escape '%{NAME}\t%{VERSION}\t%{RELEASE}\n' - so = shell_out("rpm -qa --queryformat #{format}") - pkgs = so.stdout.lines + elsif %w{rhel fedora suse}.include? platform_family + require "shellwords" + format = Shellwords.escape '%{NAME}\t%{VERSION}\t%{RELEASE}\n' + so = shell_out("rpm -qa --queryformat #{format}") + pkgs = so.stdout.lines - pkgs.each do |pkg| - name, version, release = pkg.split - packages[name] = { "version" => version, "release" => release } - end + pkgs.each do |pkg| + name, version, release = pkg.split + packages[name] = { "version" => version, "release" => release } end end end - collect_data(:windows) do - if configuration(:enabled) - packages Mash.new - require "wmi-lite" - - wmi = WmiLite::Wmi.new - w32_product = wmi.instances_of("Win32_Product") - - w32_product.find_all.each do |product| - name = product["name"] + def collect_programs_from_registry_key(key_path) + # from http://msdn.microsoft.com/en-us/library/windows/desktop/aa384129(v=vs.85).aspx + if ::RbConfig::CONFIG["target_cpu"] == "i386" + reg_type = Win32::Registry::KEY_READ | 0x100 + elsif ::RbConfig::CONFIG["target_cpu"] == "x86_64" + reg_type = Win32::Registry::KEY_READ | 0x200 + else + reg_type = Win32::Registry::KEY_READ + end + Win32::Registry::HKEY_LOCAL_MACHINE.open(key_path, reg_type) do |reg| + reg.each_key do |key, _wtime| + pkg = reg.open(key) + name = pkg["DisplayName"] rescue nil + next if name.nil? package = packages[name] = Mash.new - %w{version vendor installdate}.each do |attr| - package[attr] = product[attr] + WINDOWS_ATTRIBUTE_ALIASES.each do |registry_attr, package_attr| + value = pkg[registry_attr] rescue nil + package[package_attr] = value unless value.nil? end end end end + collect_data(:windows) do + require "win32/registry" + packages Mash.new + collect_programs_from_registry_key('Software\Microsoft\Windows\CurrentVersion\Uninstall') + # on 64 bit systems, 32 bit programs are stored here + collect_programs_from_registry_key('Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall') + end + collect_data(:aix) do - if configuration(:enabled) - packages Mash.new - so = shell_out("lslpp -L -q -c") - pkgs = so.stdout.lines + packages Mash.new + so = shell_out("lslpp -L -q -c") + pkgs = so.stdout.lines - # Output format is - # Package Name:Fileset:Level - # On aix, filesets are packages and levels are versions - pkgs.each do |pkg| - _, name, version = pkg.split(":") - packages[name] = { "version" => version } - end + # Output format is + # Package Name:Fileset:Level + # On aix, filesets are packages and levels are versions + pkgs.each do |pkg| + _, name, version = pkg.split(":") + packages[name] = { "version" => version } end end @@ -119,10 +134,8 @@ Ohai.plugin(:Packages) do end collect_data(:solaris2) do - if configuration(:enabled) - packages Mash.new - collect_ips_packages - collect_sysv_packages - end + packages Mash.new + collect_ips_packages + collect_sysv_packages end end diff --git a/spec/unit/plugins/packages_spec.rb b/spec/unit/plugins/packages_spec.rb index 169eeac6..332b89e8 100644 --- a/spec/unit/plugins/packages_spec.rb +++ b/spec/unit/plugins/packages_spec.rb @@ -20,252 +20,226 @@ require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper.rb") describe Ohai::System, "plugin packages" do - context "when the packages plugin is disabled" do - before do - Ohai.config[:plugin][:packages][:enabled] = false - allow(plugin).to receive(:collect_os).and_return(platform_family.to_s) - plugin.run - end - + context "on debian" do let(:plugin) do get_plugin("packages").tap do |plugin| - plugin[:platform_family] = platform_family + plugin[:platform_family] = "debian" end end - [:debian, :fedora, :windows, :aix, :solaris2].each do |os| - context "on #{os}" do - let(:platform_family) { os } - - it "does not enumerate the packages" do - expect(plugin[:packages]).to eq(nil) - end - end + let(:stdout) do + File.read(File.join(SPEC_PLUGIN_PATH, "dpkg-query.output")) end - end - context "when the packages plugin is enabled" do - before do - Ohai.config[:plugin][:packages][:enabled] = true + before(:each) do + allow(plugin).to receive(:collect_os).and_return(:linux) + allow(plugin).to receive(:shell_out) + .with("dpkg-query -W") + .and_return(mock_shell_out(0, stdout, "")) + plugin.run end - context "on debian" do - let(:plugin) do - get_plugin("packages").tap do |plugin| - plugin[:platform_family] = "debian" - end - end + it "calls dpkg query" do + expect(plugin).to receive(:shell_out) + .with("dpkg-query -W") + .and_return(mock_shell_out(0, stdout, "")) + plugin.run + end - let(:stdout) do - File.read(File.join(SPEC_PLUGIN_PATH, "dpkg-query.output")) - end + it "gets packages and versions" do + expect(plugin[:packages]["vim-common"][:version]).to eq("2:7.4.052-1ubuntu3") + end + end - before(:each) do - allow(plugin).to receive(:collect_os).and_return(:linux) - allow(plugin).to receive(:shell_out) - .with("dpkg-query -W") - .and_return(mock_shell_out(0, stdout, "")) - plugin.run + context "on fedora" do + let(:plugin) do + get_plugin("packages").tap do |plugin| + plugin[:platform_family] = "fedora" end + end - it "calls dpkg query" do - expect(plugin).to receive(:shell_out) - .with("dpkg-query -W") - .and_return(mock_shell_out(0, stdout, "")) - plugin.run - end + let(:format) { Shellwords.escape '%{NAME}\t%{VERSION}\t%{RELEASE}\n' } - it "gets packages and versions" do - expect(plugin[:packages]["vim-common"][:version]).to eq("2:7.4.052-1ubuntu3") - end + let(:stdout) do + File.read(File.join(SPEC_PLUGIN_PATH, "rpmquery.output")) end - context "on fedora" do - let(:plugin) do - get_plugin("packages").tap do |plugin| - plugin[:platform_family] = "fedora" - end - end + before(:each) do + allow(plugin).to receive(:collect_os).and_return(:linux) + allow(plugin).to receive(:shell_out).with("rpm -qa --queryformat #{format}").and_return(mock_shell_out(0, stdout, "")) + plugin.run + end - let(:format) { Shellwords.escape '%{NAME}\t%{VERSION}\t%{RELEASE}\n' } + it "calls rpm -qa" do + expect(plugin).to receive(:shell_out) + .with("rpm -qa --queryformat #{format}") + .and_return(mock_shell_out(0, stdout, "")) + plugin.run + end - let(:stdout) do - File.read(File.join(SPEC_PLUGIN_PATH, "rpmquery.output")) - end + it "gets packages and versions/release" do + expect(plugin[:packages]["vim-common"][:version]).to eq("7.2.411") + expect(plugin[:packages]["vim-common"][:release]).to eq("1.8.el6") + end + end - before(:each) do - allow(plugin).to receive(:collect_os).and_return(:linux) - allow(plugin).to receive(:shell_out).with("rpm -qa --queryformat #{format}").and_return(mock_shell_out(0, stdout, "")) - plugin.run - end + context "on windows", :windows_only do - it "calls rpm -qa" do - expect(plugin).to receive(:shell_out) - .with("rpm -qa --queryformat #{format}") - .and_return(mock_shell_out(0, stdout, "")) - plugin.run + let(:plugin) do + get_plugin("packages").tap do |plugin| + plugin[:platform_family] = "windows" end + end - it "gets packages and versions/release" do - expect(plugin[:packages]["vim-common"][:version]).to eq("7.2.411") - expect(plugin[:packages]["vim-common"][:release]).to eq("1.8.el6") - end + let(:win_reg_double) do + instance_double("Win32::Registry") end - context "on windows", :windows_only do - require "wmi-lite" + let(:win_reg_keys) do + [ "{22FA28AB-3C1B-438B-A8B5-E23892C8B567}", + "{0D4BCDCD-6225-4BA5-91A3-54AFCECC281E}" ] + end - let(:plugin) do - get_plugin("packages").tap do |plugin| - plugin[:platform_family] = "windows" - end - end + let(:i386_reg_type) do + Win32::Registry::KEY_READ | 0x100 + end - let(:win32_product_output) do - [{ "assignmenttype" => 0, - "caption" => "NXLOG-CE", - "description" => "NXLOG-CE", - "helplink" => nil, - "helptelephone" => nil, - "identifyingnumber" => "{22FA28AB-3C1B-438B-A8B5-E23892C8B567}", - "installdate" => "20150511", - "installdate2" => nil, - "installlocation" => nil, - "installsource" => 'C:\\chef\\cache\\', - "installstate" => 5, - "language" => "1033", - "localpackage" => 'C:\\Windows\\Installer\\30884.msi', - "name" => "NXLOG-CE", - "packagecache" => 'C:\\Windows\\Installer\\30884.msi', - "packagecode" => "{EC3A13C4-4634-47FC-9662-DC293CB96F9F}", - "packagename" => "nexlog-ce-2.8.1248.msi", - "productid" => nil, - "regcompany" => nil, - "regowner" => nil, - "skunumber" => nil, - "transforms" => nil, - "urlinfoabout" => nil, - "urlupdateinfo" => nil, - "vendor" => "nxsec.com", - "version" => "2.8.1248", - "wordcount" => 2 }, - { "assignmenttype" => 1, - "caption" => "Chef Development Kit v0.7.0", - "description" => "Chef Development Kit v0.7.0", - "helplink" => "http://www.getchef.com/support/", - "helptelephone" => nil, - "identifyingnumber" => "{90754A33-404C-4172-8F3B-7F04CE98011C}", - "installdate" => "20150925", "installdate2" => nil, - "installlocation" => nil, - "installsource" => 'C:\\Users\\skhajamohid1\\Downloads\\', - "installstate" => 5, "language" => "1033", - "localpackage" => 'C:\\WINDOWS\\Installer\\d9e1ca7.msi', - "name" => "Chef Development Kit v0.7.0", - "packagecache" => 'C:\\WINDOWS\\Installer\\d9e1ca7.msi', - "packagecode" => "{9B82FB86-40AE-4CDF-9DE8-97574F9395B9}", - "packagename" => "chefdk-0.7.0-1 (2).msi", - "productid" => nil, - "regcompany" => nil, - "regowner" => nil, - "skunumber" => nil, - "transforms" => nil, - "urlinfoabout" => nil, - "urlupdateinfo" => nil, - "vendor" => "\"Chef Software, Inc. <maintainers@chef.io>\"", - "version" => "0.7.0.1", - "wordcount" => 2 }] - end + let(:x86_64_reg_type) do + Win32::Registry::KEY_READ | 0x200 + end - before(:each) do - allow(plugin).to receive(:collect_os).and_return(:windows) - expect_any_instance_of(WmiLite::Wmi).to receive(:instances_of).with("Win32_Product").and_return(win32_product_output) - plugin.run - end + let(:win_reg_output) do + [{ "DisplayName" => "NXLOG-CE", + "DisplayVersion" => "2.8.1248", + "Publisher" => "nxsec.com", + "InstallDate" => "20150511" + }, + { "DisplayName" => "Chef Development Kit v0.7.0", + "DisplayVersion" => "0.7.0.1", + "Publisher" => "\"Chef Software, Inc. <maintainers@chef.io>\"", + "InstallDate" => "20150925" }] + end + shared_examples "windows_package_plugin" do it "gets package info" do + plugin.run expect(plugin[:packages]["Chef Development Kit v0.7.0"][:version]).to eq("0.7.0.1") - expect(plugin[:packages]["Chef Development Kit v0.7.0"][:vendor]).to eq("\"Chef Software, Inc. <maintainers@chef.io>\"") + expect(plugin[:packages]["Chef Development Kit v0.7.0"][:publisher]).to eq("\"Chef Software, Inc. <maintainers@chef.io>\"") expect(plugin[:packages]["Chef Development Kit v0.7.0"][:installdate]).to eq("20150925") expect(plugin[:packages]["NXLOG-CE"][:version]).to eq("2.8.1248") - expect(plugin[:packages]["NXLOG-CE"][:vendor]).to eq("nxsec.com") + expect(plugin[:packages]["NXLOG-CE"][:publisher]).to eq("nxsec.com") expect(plugin[:packages]["NXLOG-CE"][:installdate]).to eq("20150511") end end - context "on aix" do - let(:plugin) { get_plugin("packages") } + before(:each) do + allow(plugin).to receive(:collect_os).and_return(:windows) + allow(win_reg_double).to receive(:open).with(win_reg_keys[0]).and_return(win_reg_output[0]) + allow(win_reg_double).to receive(:open).with(win_reg_keys[1]).and_return(win_reg_output[1]) + allow(win_reg_double).to receive(:each_key).and_yield(win_reg_keys[0], 0).and_yield(win_reg_keys[1], 1) + end - let(:stdout) do - File.read(File.join(SPEC_PLUGIN_PATH, "lslpp.output")) + describe "on 32 bit ruby" do + before do + stub_const("::RbConfig::CONFIG", { "target_cpu" => "i386" } ) + allow(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open).with('Software\Microsoft\Windows\CurrentVersion\Uninstall', i386_reg_type).and_yield(win_reg_double) + allow(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open).with('Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall', i386_reg_type).and_yield(win_reg_double) end + it_behaves_like "windows_package_plugin" + end - before(:each) do - allow(plugin).to receive(:collect_os).and_return(:aix) - allow(plugin).to receive(:shell_out).with("lslpp -L -q -c").and_return(mock_shell_out(0, stdout, "")) - plugin.run + describe "on 64 bit ruby" do + before do + stub_const("::RbConfig::CONFIG", { "target_cpu" => "x86_64" } ) + allow(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open).with('Software\Microsoft\Windows\CurrentVersion\Uninstall', x86_64_reg_type).and_yield(win_reg_double) + allow(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open).with('Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall', x86_64_reg_type).and_yield(win_reg_double) end + it_behaves_like "windows_package_plugin" + end - it "calls lslpp -L -q -c" do - expect(plugin).to receive(:shell_out) - .with("lslpp -L -q -c") - .and_return(mock_shell_out(0, stdout, "")) - plugin.run + describe "on unknown ruby" do + before do + stub_const("::RbConfig::CONFIG", { "target_cpu" => nil } ) + allow(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open).with('Software\Microsoft\Windows\CurrentVersion\Uninstall', Win32::Registry::KEY_READ).and_yield(win_reg_double) + allow(Win32::Registry::HKEY_LOCAL_MACHINE).to receive(:open).with('Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall', Win32::Registry::KEY_READ).and_yield(win_reg_double) end + it_behaves_like "windows_package_plugin" + end + end - it "gets packages with version" do - expect(plugin[:packages]["chef"][:version]).to eq("12.5.1.1") - end + context "on aix" do + let(:plugin) { get_plugin("packages") } + + let(:stdout) do + File.read(File.join(SPEC_PLUGIN_PATH, "lslpp.output")) end - context "on solaris2" do - let(:plugin) { get_plugin("packages") } + before(:each) do + allow(plugin).to receive(:collect_os).and_return(:aix) + allow(plugin).to receive(:shell_out).with("lslpp -L -q -c").and_return(mock_shell_out(0, stdout, "")) + plugin.run + end - let(:pkglist_output) do - File.read(File.join(SPEC_PLUGIN_PATH, "pkglist.output")) - end + it "calls lslpp -L -q -c" do + expect(plugin).to receive(:shell_out) + .with("lslpp -L -q -c") + .and_return(mock_shell_out(0, stdout, "")) + plugin.run + end - let(:pkginfo_output) do - File.read(File.join(SPEC_PLUGIN_PATH, "pkginfo.output")) - end + it "gets packages with version" do + expect(plugin[:packages]["chef"][:version]).to eq("12.5.1.1") + end + end - before(:each) do - allow(plugin).to receive(:collect_os).and_return(:solaris2) - allow(plugin).to receive(:shell_out).with("pkg list -H").and_return(mock_shell_out(0, pkglist_output, "")) - allow(plugin).to receive(:shell_out).with("pkginfo -l").and_return(mock_shell_out(0, pkginfo_output, "")) - plugin.run - end + context "on solaris2" do + let(:plugin) { get_plugin("packages") } - it "calls pkg list -H" do - expect(plugin).to receive(:shell_out) - .with("pkg list -H") - .and_return(mock_shell_out(0, pkglist_output, "")) - plugin.run - end + let(:pkglist_output) do + File.read(File.join(SPEC_PLUGIN_PATH, "pkglist.output")) + end - it "calls pkginfo -l" do - expect(plugin).to receive(:shell_out) - .with("pkginfo -l") - .and_return(mock_shell_out(0, pkginfo_output, "")) - plugin.run - end + let(:pkginfo_output) do + File.read(File.join(SPEC_PLUGIN_PATH, "pkginfo.output")) + end - it "gets ips packages with version" do - expect(plugin[:packages]["chef"][:version]).to eq("12.5.1") - end + before(:each) do + allow(plugin).to receive(:collect_os).and_return(:solaris2) + allow(plugin).to receive(:shell_out).with("pkg list -H").and_return(mock_shell_out(0, pkglist_output, "")) + allow(plugin).to receive(:shell_out).with("pkginfo -l").and_return(mock_shell_out(0, pkginfo_output, "")) + plugin.run + end - it "gets ips packages with version and publisher" do - expect(plugin[:packages]["system/EMCpower"][:version]).to eq("6.0.0.1.0-3") - expect(plugin[:packages]["system/EMCpower"][:publisher]).to eq("emc.com") - end + it "calls pkg list -H" do + expect(plugin).to receive(:shell_out) + .with("pkg list -H") + .and_return(mock_shell_out(0, pkglist_output, "")) + plugin.run + end - it "gets sysv packages with version" do - expect(plugin[:packages]["chef"][:version]).to eq("12.5.1") - end + it "calls pkginfo -l" do + expect(plugin).to receive(:shell_out) + .with("pkginfo -l") + .and_return(mock_shell_out(0, pkginfo_output, "")) + plugin.run + end - it "gets sysv packages with version" do - expect(plugin[:packages]["mqm"][:version]).to eq("7.0.1.4") - end + it "gets ips packages with version" do + expect(plugin[:packages]["chef"][:version]).to eq("12.5.1") + end + + it "gets ips packages with version and publisher" do + expect(plugin[:packages]["system/EMCpower"][:version]).to eq("6.0.0.1.0-3") + expect(plugin[:packages]["system/EMCpower"][:publisher]).to eq("emc.com") + end + + it "gets sysv packages with version" do + expect(plugin[:packages]["chef"][:version]).to eq("12.5.1") + end + + it "gets sysv packages with version" do + expect(plugin[:packages]["mqm"][:version]).to eq("7.0.1.4") end end end |