diff --git a/lib/puppet/provider/package/msi.rb b/lib/puppet/provider/package/msi.rb deleted file mode 100644 index 0edd024ec..000000000 --- a/lib/puppet/provider/package/msi.rb +++ /dev/null @@ -1,125 +0,0 @@ -require 'puppet/provider/package' -require 'puppet/util/windows' - -Puppet::Type.type(:package).provide(:msi, :parent => Puppet::Provider::Package) do - desc "Windows package management by installing and removing MSIs. - - The `msi` provider is deprecated. Use the `windows` provider instead." - - confine :operatingsystem => :windows - - has_feature :installable - has_feature :uninstallable - has_feature :install_options - has_feature :uninstall_options - - class MsiPackage - extend Enumerable - include Puppet::Util::Windows::Registry - extend Puppet::Util::Windows::Registry - - def self.installer - WIN32OLE.new("WindowsInstaller.Installer") - end - - def self.each(&block) - inst = installer - inst.UILevel = 2 - - inst.Products.each do |guid| - # products may be advertised, installed in a different user - # context, etc, we only want to know about products currently - # installed in our context. - next unless inst.ProductState(guid) == 5 - - package = { - :name => inst.ProductInfo(guid, 'ProductName'), - # although packages have a version, the provider isn't versionable, - # so we can't return a version - # :ensure => inst.ProductInfo(guid, 'VersionString'), - :ensure => :installed, - :provider => :msi, - :productcode => guid, - :packagecode => inst.ProductInfo(guid, 'PackageCode') - } - - yield package - end - end - end - - def self.instances - [] - end - - def initialize(resource = nil) - Puppet.deprecation_warning "The `:msi` package provider is deprecated, use the `:windows` provider instead." - super(resource) - end - - # Find first package whose PackageCode, e.g. {B2BE95D2-CD2C-46D6-8D27-35D150E58EC9}, - # matches the resource name (case-insensitively due to hex) or the ProductName matches - # the resource name. The ProductName is not guaranteed to be unique, but the PackageCode - # should be if the package is authored correctly. - def query - MsiPackage.enum_for.find do |package| - resource[:name].casecmp(package[:packagecode]) == 0 || resource[:name] == package[:name] - end - end - - def install - fail("The source parameter is required when using the MSI provider.") unless resource[:source] - - # Unfortunately, we can't use the msiexec method defined earlier, - # because of the special quoting we need to do around the MSI - # properties to use. - command = ['msiexec.exe', '/qn', '/norestart', '/i', shell_quote(resource[:source]), install_options].flatten.compact.join(' ') - output = execute(command, :failonfail => false, :combine => true) - - check_result(output.exitstatus) - end - - def uninstall - fail("The productcode property is missing.") unless properties[:productcode] - - command = ['msiexec.exe', '/qn', '/norestart', '/x', properties[:productcode], uninstall_options].flatten.compact.join(' ') - output = execute(command, :failonfail => false, :combine => true) - - check_result(output.exitstatus) - end - - # (Un)install may "fail" because the package requested a reboot, the system requested a - # reboot, or something else entirely. Reboot requests mean the package was installed - # successfully, but we warn since we don't have a good reboot strategy. - def check_result(hr) - operation = resource[:ensure] == :absent ? 'uninstall' : 'install' - - # http://msdn.microsoft.com/en-us/library/windows/desktop/aa368542(v=vs.85).aspx - case hr - when 0 - # yeah - when 1641 - warning("The package #{operation}ed successfully and the system is rebooting now.") - when 3010 - warning("The package #{operation}ed successfully, but the system must be rebooted.") - else - raise Puppet::Util::Windows::Error.new("Failed to #{operation}", hr) - end - end - - def validate_source(value) - fail("The source parameter cannot be empty when using the MSI provider.") if value.empty? - end - - def install_options - join_options(resource[:install_options]) - end - - def uninstall_options - join_options(resource[:uninstall_options]) - end - - def shell_quote(value) - value.include?(' ') ? %Q["#{value.gsub(/"/, '\"')}"] : value - end -end diff --git a/spec/integration/provider/package_spec.rb b/spec/integration/provider/package_spec.rb index aecc960a6..9a21e4558 100755 --- a/spec/integration/provider/package_spec.rb +++ b/spec/integration/provider/package_spec.rb @@ -1,44 +1,35 @@ #! /usr/bin/env ruby require 'spec_helper' describe "Package provider" do include PuppetSpec::Files Puppet::Type.type(:package).providers.each do |name| provider = Puppet::Type.type(:package).provider(name) describe name, :if => provider.suitable? do it "should fail when asked to install an invalid package" do pending("This test hangs forever with recent versions of RubyGems") if provider.name == :gem options = {:name => "nosuch#{provider.name}", :provider => provider.name} - # The MSI provider requires that source be specified as it is - # what actually determines if the package exists. - if provider.name == :msi - options[:source] = tmpfile("msi_package") - end pkg = Puppet::Type.newpackage(options) lambda { pkg.provider.install }.should raise_error end it "should be able to get a list of existing packages" do - if provider.name == :msi - Puppet[:vardir] = tmpdir('msi_package_var_dir') - end - # the instances method requires root priviledges on gentoo # if the eix cache is outdated (to run eix-update) so make # sure we dont actually run eix-update if provider.name == :portage provider.stubs(:update_eix).returns('Database contains 15240 packages in 155 categories') end provider.instances.each do |package| package.should be_instance_of(provider) package.properties[:provider].should == provider.name end end end end end diff --git a/spec/unit/provider/package/msi_spec.rb b/spec/unit/provider/package/msi_spec.rb deleted file mode 100755 index 9c325bb49..000000000 --- a/spec/unit/provider/package/msi_spec.rb +++ /dev/null @@ -1,229 +0,0 @@ -#! /usr/bin/env ruby -require 'spec_helper' - -describe Puppet::Type.type(:package).provider(:msi) do - let (:name) { 'mysql-5.1.58-win-x64' } - let (:source) { 'E:\mysql-5.1.58-win-x64.msi' } - let (:productcode) { '{E437FFB6-5C49-4DAC-ABAE-33FF065FE7CC}' } - let (:packagecode) { '{5A6FD560-763A-4BC1-9E03-B18DFFB7C72C}' } - let (:resource) { Puppet::Type.type(:package).new(:name => name, :provider => :msi, :source => source) } - let (:provider) { resource.provider } - let (:execute_options) do {:failonfail => false, :combine => true} end - - def installer(productcodes) - installer = mock - installer.expects(:UILevel=).with(2) - - installer.stubs(:ProductState).returns(5) - installer.stubs(:Products).returns(productcodes) - productcodes.each do |guid| - installer.stubs(:ProductInfo).with(guid, 'ProductName').returns("name-#{guid}") - installer.stubs(:ProductInfo).with(guid, 'PackageCode').returns("package-#{guid}") - end - - MsiPackage.stubs(:installer).returns(installer) - end - - def expect_execute(command, status) - provider.expects(:execute).with(command, execute_options).returns(Puppet::Util::Execution::ProcessOutput.new('',status)) - end - - describe 'provider features' do - it { should be_installable } - it { should be_uninstallable } - it { should be_install_options } - it { should be_uninstall_options } - end - - describe 'on Windows', :as_platform => :windows do - after :each do - Puppet::Type.type(:package).defaultprovider = nil - end - - it 'should not be the default provider' do - # provider.expects(:execute).never - Puppet::Type.type(:package).defaultprovider.should_not == subject.class - end - end - - context '::instances' do - it 'should return an empty array' do - described_class.instances.should == [] - end - end - - context '#initialize' do - it 'should issue a deprecation warning' do - Puppet.expects(:deprecation_warning).with("The `:msi` package provider is deprecated, use the `:windows` provider instead.") - - Puppet::Type.type(:package).new(:name => name, :provider => :msi, :source => source) - end - end - - context '#query' do - let (:package) do { - :name => name, - :ensure => :installed, - :provider => :msi, - :productcode => productcode, - :packagecode => packagecode.upcase - } - end - - before :each do - MsiPackage.stubs(:each).yields(package) - end - - it 'should match package codes case-insensitively' do - resource[:name] = packagecode.downcase - - provider.query.should == package - end - - it 'should match product name' do - resource[:name] = name - - provider.query.should == package - end - - it 'should return nil if none found' do - resource[:name] = 'not going to find it' - - provider.query.should be_nil - end - end - - context '#install' do - let (:command) { "msiexec.exe /qn /norestart /i #{source}" } - - it 'should require the source parameter' do - resource = Puppet::Type.type(:package).new(:name => name, :provider => :msi) - - expect do - resource.provider.install - end.to raise_error(Puppet::Error, /The source parameter is required when using the MSI provider/) - end - - it 'should install using the source and install_options' do - resource[:install_options] = { 'INSTALLDIR' => 'C:\mysql-5.1' } - expect_execute("#{command} INSTALLDIR=C:\\mysql-5.1", 0) - - provider.install - end - - it 'should warn if reboot initiated' do - expect_execute(command, 1641) - provider.expects(:warning).with('The package installed successfully and the system is rebooting now.') - - provider.install - end - - it 'should warn if reboot required' do - expect_execute(command, 3010) - provider.expects(:warning).with('The package installed successfully, but the system must be rebooted.') - - provider.install - end - - it 'should fail otherwise', :if => Puppet.features.microsoft_windows? do - expect_execute(command, 5) - - expect do - provider.install - end.to raise_error(Puppet::Util::Windows::Error, /Access is denied/) - end - end - - context '#uninstall' do - - let (:command) { "msiexec.exe /qn /norestart /x #{productcode}" } - - before :each do - resource[:ensure] = :absent - provider.set(:productcode => productcode) - end - - it 'should require the productcode' do - provider.set(:productcode => nil) - expect do - provider.uninstall - end.to raise_error(Puppet::Error, /The productcode property is missing./) - end - - it 'should uninstall using the productcode' do - expect_execute(command, 0) - - provider.uninstall - end - - it 'should warn if reboot initiated' do - expect_execute(command, 1641) - provider.expects(:warning).with('The package uninstalled successfully and the system is rebooting now.') - - provider.uninstall - end - - it 'should warn if reboot required' do - expect_execute(command, 3010) - provider.expects(:warning).with('The package uninstalled successfully, but the system must be rebooted.') - - provider.uninstall - end - - it 'should fail otherwise', :if => Puppet.features.microsoft_windows? do - expect_execute(command, 5) - - expect do - provider.uninstall - end.to raise_error(Puppet::Util::Windows::Error, /Failed to uninstall.*Access is denied/) - end - end - - context '#validate_source' do - it 'should fail if the source parameter is empty' do - expect do - resource[:source] = '' - end.to raise_error(Puppet::Error, /The source parameter cannot be empty when using the MSI provider/) - end - - it 'should accept a source' do - resource[:source] = source - end - end - - context '#install_options' do - it 'should return nil by default' do - provider.install_options.should be_nil - end - - it 'should return the options' do - resource[:install_options] = { 'INSTALLDIR' => 'C:\mysql-here' } - - provider.install_options.should == ['INSTALLDIR=C:\mysql-here'] - end - - it 'should only quote if needed' do - resource[:install_options] = { 'INSTALLDIR' => 'C:\mysql here' } - - provider.install_options.should == ['INSTALLDIR="C:\mysql here"'] - end - - it 'should escape embedded quotes in install_options values with spaces' do - resource[:install_options] = { 'INSTALLDIR' => 'C:\mysql "here"' } - - provider.install_options.should == ['INSTALLDIR="C:\mysql \"here\""'] - end - end - - context '#uninstall_options' do - it 'should return nil by default' do - provider.uninstall_options.should be_nil - end - - it 'should return the options' do - resource[:uninstall_options] = { 'INSTALLDIR' => 'C:\mysql-here' } - - provider.uninstall_options.should == ['INSTALLDIR=C:\mysql-here'] - end - end -end