summaryrefslogtreecommitdiff
path: root/lib/chef/provider/package
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef/provider/package')
-rw-r--r--lib/chef/provider/package/apt.rb147
-rw-r--r--lib/chef/provider/package/dpkg.rb128
-rw-r--r--lib/chef/provider/package/easy_install.rb136
-rw-r--r--lib/chef/provider/package/freebsd.rb149
-rw-r--r--lib/chef/provider/package/ips.rb101
-rw-r--r--lib/chef/provider/package/macports.rb105
-rw-r--r--lib/chef/provider/package/pacman.rb111
-rw-r--r--lib/chef/provider/package/portage.rb138
-rw-r--r--lib/chef/provider/package/rpm.rb121
-rw-r--r--lib/chef/provider/package/rubygems.rb548
-rw-r--r--lib/chef/provider/package/smartos.rb84
-rw-r--r--lib/chef/provider/package/solaris.rb139
-rw-r--r--lib/chef/provider/package/yum-dump.py287
-rw-r--r--lib/chef/provider/package/yum.rb1214
-rw-r--r--lib/chef/provider/package/zypper.rb144
15 files changed, 3552 insertions, 0 deletions
diff --git a/lib/chef/provider/package/apt.rb b/lib/chef/provider/package/apt.rb
new file mode 100644
index 0000000000..e8939b494e
--- /dev/null
+++ b/lib/chef/provider/package/apt.rb
@@ -0,0 +1,147 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, 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 'chef/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'chef/mixin/shell_out'
+
+
+class Chef
+ class Provider
+ class Package
+ class Apt < Chef::Provider::Package
+
+ include Chef::Mixin::ShellOut
+ attr_accessor :is_virtual_package
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+ check_package_state(@new_resource.package_name)
+ @current_resource
+ end
+
+ def default_release_options
+ # Use apt::Default-Release option only if provider was explicitly defined
+ "-o APT::Default-Release=#{@new_resource.default_release}" if @new_resource.provider && @new_resource.default_release
+ end
+
+ def check_package_state(package)
+ Chef::Log.debug("#{@new_resource} checking package status for #{package}")
+ installed = false
+
+ shell_out!("apt-cache#{expand_options(default_release_options)} policy #{package}").stdout.each_line do |line|
+ case line
+ when /^\s{2}Installed: (.+)$/
+ installed_version = $1
+ if installed_version == '(none)'
+ Chef::Log.debug("#{@new_resource} current version is nil")
+ @current_resource.version(nil)
+ else
+ Chef::Log.debug("#{@new_resource} current version is #{installed_version}")
+ @current_resource.version(installed_version)
+ installed = true
+ end
+ when /^\s{2}Candidate: (.+)$/
+ candidate_version = $1
+ if candidate_version == '(none)'
+ # This may not be an appropriate assumption, but it shouldn't break anything that already worked -- btm
+ @is_virtual_package = true
+ showpkg = shell_out!("apt-cache showpkg #{package}").stdout
+ providers = Hash.new
+ showpkg.rpartition(/Reverse Provides:? #{$/}/)[2].each_line do |line|
+ provider, version = line.split
+ providers[provider] = version
+ end
+ # Check if the package providing this virtual package is installed
+ num_providers = providers.length
+ raise Chef::Exceptions::Package, "#{@new_resource.package_name} has no candidate in the apt-cache" if num_providers == 0
+ # apt will only install a virtual package if there is a single providing package
+ raise Chef::Exceptions::Package, "#{@new_resource.package_name} is a virtual package provided by #{num_providers} packages, you must explicitly select one to install" if num_providers > 1
+ # Check if the package providing this virtual package is installed
+ Chef::Log.info("#{@new_resource} is a virtual package, actually acting on package[#{providers.keys.first}]")
+ installed = check_package_state(providers.keys.first)
+ else
+ Chef::Log.debug("#{@new_resource} candidate version is #{$1}")
+ @candidate_version = $1
+ end
+ end
+ end
+
+ return installed
+ end
+
+ def install_package(name, version)
+ package_name = "#{name}=#{version}"
+ package_name = name if @is_virtual_package
+ run_command_with_systems_locale(
+ :command => "apt-get -q -y#{expand_options(default_release_options)}#{expand_options(@new_resource.options)} install #{package_name}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ package_name = "#{name}"
+ run_command_with_systems_locale(
+ :command => "apt-get -q -y#{expand_options(@new_resource.options)} remove #{package_name}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+
+ def purge_package(name, version)
+ run_command_with_systems_locale(
+ :command => "apt-get -q -y#{expand_options(@new_resource.options)} purge #{@new_resource.package_name}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+
+ def preseed_package(preseed_file)
+ Chef::Log.info("#{@new_resource} pre-seeding package installation instructions")
+ run_command_with_systems_locale(
+ :command => "debconf-set-selections #{preseed_file}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+
+ def reconfig_package(name, version)
+ Chef::Log.info("#{@new_resource} reconfiguring")
+ run_command_with_systems_locale(
+ :command => "dpkg-reconfigure #{name}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/dpkg.rb b/lib/chef/provider/package/dpkg.rb
new file mode 100644
index 0000000000..795a7b308b
--- /dev/null
+++ b/lib/chef/provider/package/dpkg.rb
@@ -0,0 +1,128 @@
+#
+# Author:: Bryan McLellan (btm@loftninjas.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan
+# 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 'chef/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'chef/mixin/get_source_from_package'
+
+class Chef
+ class Provider
+ class Package
+ class Dpkg < Chef::Provider::Package::Apt
+ DPKG_INFO = /([a-z\d\-\+\.]+)\t([\w\d.~-]+)/
+ DPKG_INSTALLED = /^Status: install ok installed/
+ DPKG_VERSION = /^Version: (.+)$/
+
+ include Chef::Mixin::GetSourceFromPackage
+ def define_resource_requirements
+ super
+ requirements.assert(:install) do |a|
+ a.assertion{ not @new_resource.source.nil? }
+ a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
+ end
+
+ # TODO this was originally written for any action in which .source is provided
+ # but would it make more sense to only look at source if the action is :install?
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @source_exists }
+ a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
+ a.whyrun "Assuming it would have been previously downloaded."
+ end
+ end
+
+ def load_current_resource
+ @source_exists = true
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+ @new_resource.version(nil)
+
+ if @new_resource.source
+ @source_exists = ::File.exists?(@new_resource.source)
+ if @source_exists
+ # Get information from the package if supplied
+ Chef::Log.debug("#{@new_resource} checking dpkg status")
+ status = popen4("dpkg-deb -W #{@new_resource.source}") do |pid, stdin, stdout, stderr|
+ stdout.each_line do |line|
+ if pkginfo = DPKG_INFO.match(line)
+ @current_resource.package_name(pkginfo[1])
+ @new_resource.version(pkginfo[2])
+ end
+ end
+ end
+ else
+ # Source provided but not valid means we can't safely do further processing
+ return
+ end
+
+ end
+
+ # Check to see if it is installed
+ package_installed = nil
+ Chef::Log.debug("#{@new_resource} checking install state")
+ status = popen4("dpkg -s #{@current_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdout.each_line do |line|
+ case line
+ when DPKG_INSTALLED
+ package_installed = true
+ when DPKG_VERSION
+ if package_installed
+ Chef::Log.debug("#{@new_resource} current version is #{$1}")
+ @current_resource.version($1)
+ end
+ end
+ end
+ end
+
+ unless status.exitstatus == 0 || status.exitstatus == 1
+ raise Chef::Exceptions::Package, "dpkg failed - #{status.inspect}!"
+ end
+
+ @current_resource
+ end
+
+ def install_package(name, version)
+ run_command_with_systems_locale(
+ :command => "dpkg -i#{expand_options(@new_resource.options)} #{@new_resource.source}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+
+ def remove_package(name, version)
+ run_command_with_systems_locale(
+ :command => "dpkg -r#{expand_options(@new_resource.options)} #{@new_resource.package_name}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+
+ def purge_package(name, version)
+ run_command_with_systems_locale(
+ :command => "dpkg -P#{expand_options(@new_resource.options)} #{@new_resource.package_name}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/easy_install.rb b/lib/chef/provider/package/easy_install.rb
new file mode 100644
index 0000000000..6c9dacc55d
--- /dev/null
+++ b/lib/chef/provider/package/easy_install.rb
@@ -0,0 +1,136 @@
+#
+# Author:: Joe Williams (<joe@joetify.com>)
+# Copyright:: Copyright (c) 2009 Joe Williams
+# 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 'chef/provider/package'
+require 'chef/mixin/command'
+require 'chef/mixin/shell_out'
+require 'chef/resource/package'
+require 'chef/mixin/shell_out'
+
+class Chef
+ class Provider
+ class Package
+ class EasyInstall < Chef::Provider::Package
+
+ include Chef::Mixin::ShellOut
+
+ def install_check(name)
+ check = false
+
+ begin
+ # first check to see if we can import it
+ output = shell_out!("#{python_binary_path} -c \"import #{name}\"", :returns=>[0,1]).stderr
+ if output.include? "ImportError"
+ # then check to see if its on the path
+ output = shell_out!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns=>[0,1]).stdout
+ if output.downcase.include? "#{name.downcase}"
+ check = true
+ end
+ else
+ check = true
+ end
+ rescue
+ # it's probably not installed
+ end
+
+ check
+ end
+
+ def easy_install_binary_path
+ path = @new_resource.easy_install_binary
+ path ? path : 'easy_install'
+ end
+
+ def python_binary_path
+ path = @new_resource.python_binary
+ path ? path : 'python'
+ end
+
+ def module_name
+ m = @new_resource.module_name
+ m ? m : @new_resource.name
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+ @current_resource.version(nil)
+
+ # get the currently installed version if installed
+ package_version = nil
+ if install_check(module_name)
+ begin
+ output = shell_out!("#{python_binary_path} -c \"import #{module_name}; print #{module_name}.__version__\"").stdout
+ package_version = output.strip
+ rescue
+ output = shell_out!("#{python_binary_path} -c \"import sys; print sys.path\"", :returns=>[0,1]).stdout
+
+ output_array = output.gsub(/[\[\]]/,'').split(/\s*,\s*/)
+ package_path = ""
+
+ output_array.each do |entry|
+ if entry.downcase.include?(@new_resource.package_name)
+ package_path = entry
+ end
+ end
+
+ package_path[/\S\S(.*)\/(.*)-(.*)-py(.*).egg\S/]
+ package_version = $3
+ end
+ end
+
+ if package_version == @new_resource.version
+ Chef::Log.debug("#{@new_resource} at version #{@new_resource.version}")
+ @current_resource.version(@new_resource.version)
+ else
+ Chef::Log.debug("#{@new_resource} at version #{package_version}")
+ @current_resource.version(package_version)
+ end
+
+ @current_resource
+ end
+
+ def candidate_version
+ return @candidate_version if @candidate_version
+
+ # do a dry run to get the latest version
+ result = shell_out!("#{easy_install_binary_path} -n #{@new_resource.package_name}", :returns=>[0,1])
+ @candidate_version = result.stdout[/(.*)Best match: (.*) (.*)$/, 3]
+ @candidate_version
+ end
+
+ def install_package(name, version)
+ run_command(:command => "#{easy_install_binary_path}#{expand_options(@new_resource.options)} \"#{name}==#{version}\"")
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ run_command(:command => "#{easy_install_binary_path }#{expand_options(@new_resource.options)} -m #{name}")
+ end
+
+ def purge_package(name, version)
+ remove_package(name, version)
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/freebsd.rb b/lib/chef/provider/package/freebsd.rb
new file mode 100644
index 0000000000..afdd0d812e
--- /dev/null
+++ b/lib/chef/provider/package/freebsd.rb
@@ -0,0 +1,149 @@
+#
+# Authors:: Bryan McLellan (btm@loftninjas.org)
+# Matthew Landauer (matthew@openaustralia.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan, Matthew Landauer
+# 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 'chef/provider/package'
+require 'chef/mixin/shell_out'
+require 'chef/resource/package'
+require 'chef/mixin/get_source_from_package'
+
+class Chef
+ class Provider
+ class Package
+ class Freebsd < Chef::Provider::Package
+ include Chef::Mixin::ShellOut
+
+ include Chef::Mixin::GetSourceFromPackage
+
+ def initialize(*args)
+ super
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ end
+
+ def current_installed_version
+ pkg_info = shell_out!("pkg_info -E \"#{package_name}*\"", :env => nil, :returns => [0,1])
+ pkg_info.stdout[/^#{package_name}-(.+)/, 1]
+ end
+
+ def port_path
+ case @new_resource.package_name
+ # When the package name starts with a '/' treat it as the full path to the ports directory
+ when /^\//
+ @new_resource.package_name
+ # Otherwise if the package name contains a '/' not at the start (like 'www/wordpress') treat as a relative
+ # path from /usr/ports
+ when /\//
+ "/usr/ports/#{@new_resource.package_name}"
+ # Otherwise look up the path to the ports directory using 'whereis'
+ else
+ whereis = shell_out!("whereis -s #{@new_resource.package_name}", :env => nil)
+ unless path = whereis.stdout[/^#{@new_resource.package_name}:\s+(.+)$/, 1]
+ raise Chef::Exceptions::Package, "Could not find port with the name #{@new_resource.package_name}"
+ end
+ path
+ end
+ end
+
+ def ports_makefile_variable_value(variable)
+ make_v = shell_out!("make -V #{variable}", :cwd => port_path, :env => nil, :returns => [0,1])
+ make_v.stdout.strip.split($\).first # $\ is the line separator, i.e., newline
+ end
+
+ def ports_candidate_version
+ ports_makefile_variable_value("PORTVERSION")
+ end
+
+ def file_candidate_version_path
+ Dir["#{@new_resource.source}/#{@current_resource.package_name}*"][-1].to_s
+ end
+
+ def file_candidate_version
+ file_candidate_version_path.split(/-/).last.split(/.tbz/).first
+ end
+
+ def load_current_resource
+ @current_resource.package_name(@new_resource.package_name)
+
+ @current_resource.version(current_installed_version)
+ Chef::Log.debug("#{@new_resource} current version is #{@current_resource.version}") if @current_resource.version
+
+ case @new_resource.source
+ when /^http/, /^ftp/
+ @candidate_version = "0.0.0"
+ when /^\//
+ @candidate_version = file_candidate_version
+ else
+ @candidate_version = ports_candidate_version
+ end
+
+ Chef::Log.debug("#{@new_resource} ports candidate version is #{@candidate_version}") if @candidate_version
+
+ @current_resource
+ end
+
+ def latest_link_name
+ ports_makefile_variable_value("LATEST_LINK")
+ end
+
+ # The name of the package (without the version number) as understood by pkg_add and pkg_info
+ def package_name
+ if ::File.exist?("/usr/ports/Makefile")
+ if ports_makefile_variable_value("PKGNAME") =~ /^(.+)-[^-]+$/
+ $1
+ else
+ raise Chef::Exceptions::Package, "Unexpected form for PKGNAME variable in #{port_path}/Makefile"
+ end
+ else
+ @new_resource.package_name
+ end
+ end
+
+ def install_package(name, version)
+ unless @current_resource.version
+ case @new_resource.source
+ when /^ports$/
+ shell_out!("make -DBATCH install", :timeout => 1200, :env => nil, :cwd => port_path).status
+ when /^http/, /^ftp/
+ if @new_resource.source =~ /\/$/
+ shell_out!("pkg_add -r #{package_name}", :env => { "PACKAGESITE" => @new_resource.source, 'LC_ALL' => nil }).status
+ else
+ shell_out!("pkg_add -r #{package_name}", :env => { "PACKAGEROOT" => @new_resource.source, 'LC_ALL' => nil }).status
+ end
+ Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}")
+ when /^\//
+ shell_out!("pkg_add #{file_candidate_version_path}", :env => { "PKG_PATH" => @new_resource.source , 'LC_ALL'=>nil}).status
+ Chef::Log.debug("#{@new_resource} installed from: #{@new_resource.source}")
+ else
+ shell_out!("pkg_add -r #{latest_link_name}", :env => nil).status
+ end
+ end
+ end
+
+ def remove_package(name, version)
+ # a version is mandatory
+ if version
+ shell_out!("pkg_delete #{package_name}-#{version}", :env => nil).status
+ else
+ shell_out!("pkg_delete #{package_name}-#{@current_resource.version}", :env => nil).status
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/ips.rb b/lib/chef/provider/package/ips.rb
new file mode 100644
index 0000000000..5beb46a20a
--- /dev/null
+++ b/lib/chef/provider/package/ips.rb
@@ -0,0 +1,101 @@
+#
+# Author:: Jason J. W. Williams (<williamsjj@digitar.com>)
+# Author:: Stephen Nelson-Smith (<sns@opscode.com>)
+# Copyright:: Copyright (c) 2011 Opscode, 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 'open3'
+require 'chef/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'chef/mixin/shell_out'
+
+class Chef
+ class Provider
+ class Package
+ class Ips < Chef::Provider::Package
+
+ include Chef::Mixin::ShellOut
+ attr_accessor :virtual
+
+ def define_resource_requirements
+ super
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { ! @candidate_version.nil? }
+ a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.package_name} not found"
+ a.whyrun "Assuming package #{@new_resource.package_name} would have been made available."
+ end
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.name)
+ check_package_state(@new_resource.package_name)
+ @current_resource
+ end
+
+ def check_package_state(package)
+ Chef::Log.debug("Checking package status for #{package}")
+ installed = false
+ depends = false
+
+ shell_out!("pkg info -r #{package}").stdout.each_line do |line|
+ case line
+ when /^\s+State: Installed/
+ installed = true
+ when /^\s+Version: (.*)/
+ @candidate_version = $1.split[0]
+ if installed
+ @current_resource.version($1)
+ else
+ @current_resource.version(nil)
+ end
+ end
+ end
+
+ return installed
+ end
+
+ def install_package(name, version)
+ package_name = "#{name}@#{version}"
+ normal_command = "pkg#{expand_options(@new_resource.options)} install -q #{package_name}"
+ if @new_resource.respond_to?(:accept_license) and @new_resource.accept_license
+ command = normal_command.gsub('-q', '-q --accept')
+ else
+ command = normal_command
+ end
+ begin
+ run_command_with_systems_locale(:command => command)
+ rescue
+ end
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ package_name = "#{name}@#{version}"
+ run_command_with_systems_locale(
+ :command => "pkg#{expand_options(@new_resource.options)} uninstall -q #{package_name}"
+ )
+ end
+ end
+ end
+ end
+end
+
diff --git a/lib/chef/provider/package/macports.rb b/lib/chef/provider/package/macports.rb
new file mode 100644
index 0000000000..fd33788944
--- /dev/null
+++ b/lib/chef/provider/package/macports.rb
@@ -0,0 +1,105 @@
+class Chef
+ class Provider
+ class Package
+ class Macports < Chef::Provider::Package
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+
+ @current_resource.version(current_installed_version)
+ Chef::Log.debug("#{@new_resource} current version is #{@current_resource.version}") if @current_resource.version
+
+ @candidate_version = macports_candidate_version
+
+ if !@new_resource.version and !@candidate_version
+ raise Chef::Exceptions::Package, "Could not get a candidate version for this package -- #{@new_resource.name} does not seem to be a valid package!"
+ end
+
+ Chef::Log.debug("#{@new_resource} candidate version is #{@candidate_version}") if @candidate_version
+
+ @current_resource
+ end
+
+ def current_installed_version
+ command = "port installed #{@new_resource.package_name}"
+ output = get_response_from_command(command)
+
+ response = nil
+ output.each_line do |line|
+ match = line.match(/^.+ @([^\s]+) \(active\)$/)
+ response = match[1] if match
+ end
+ response
+ end
+
+ def macports_candidate_version
+ command = "port info --version #{@new_resource.package_name}"
+ output = get_response_from_command(command)
+
+ match = output.match(/^version: (.+)$/)
+
+ match ? match[1] : nil
+ end
+
+ def install_package(name, version)
+ unless @current_resource.version == version
+ command = "port#{expand_options(@new_resource.options)} install #{name}"
+ command << " @#{version}" if version and !version.empty?
+ run_command_with_systems_locale(
+ :command => command
+ )
+ end
+ end
+
+ def purge_package(name, version)
+ command = "port#{expand_options(@new_resource.options)} uninstall #{name}"
+ command << " @#{version}" if version and !version.empty?
+ run_command_with_systems_locale(
+ :command => command
+ )
+ end
+
+ def remove_package(name, version)
+ command = "port#{expand_options(@new_resource.options)} deactivate #{name}"
+ command << " @#{version}" if version and !version.empty?
+
+ run_command_with_systems_locale(
+ :command => command
+ )
+ end
+
+ def upgrade_package(name, version)
+ # Saving this to a variable -- weird rSpec behavior
+ # happens otherwise...
+ current_version = @current_resource.version
+
+ if current_version.nil? or current_version.empty?
+ # Macports doesn't like when you upgrade a package
+ # that hasn't been installed.
+ install_package(name, version)
+ elsif current_version != version
+ run_command_with_systems_locale(
+ :command => "port#{expand_options(@new_resource.options)} upgrade #{name} @#{version}"
+ )
+ end
+ end
+
+ private
+ def get_response_from_command(command)
+ output = nil
+ status = popen4(command) do |pid, stdin, stdout, stderr|
+ begin
+ output = stdout.read
+ rescue Exception
+ raise Chef::Exceptions::Package, "Could not read from STDOUT on command: #{command}"
+ end
+ end
+ unless status.exitstatus == 0 || status.exitstatus == 1
+ raise Chef::Exceptions::Package, "#{command} failed - #{status.insect}!"
+ end
+ output
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/pacman.rb b/lib/chef/provider/package/pacman.rb
new file mode 100644
index 0000000000..f81486ae84
--- /dev/null
+++ b/lib/chef/provider/package/pacman.rb
@@ -0,0 +1,111 @@
+#
+# Author:: Jan Zimmek (<jan.zimmek@web.de>)
+# Copyright:: Copyright (c) 2010 Jan Zimmek
+# 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 'chef/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+
+class Chef
+ class Provider
+ class Package
+ class Pacman < Chef::Provider::Package
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+
+ @current_resource.version(nil)
+
+ Chef::Log.debug("#{@new_resource} checking pacman for #{@new_resource.package_name}")
+ status = popen4("pacman -Qi #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ line.force_encoding(Encoding::UTF_8) if line.respond_to?(:force_encoding)
+ case line
+ when /^Version(\s?)*: (.+)$/
+ Chef::Log.debug("#{@new_resource} current version is #{$2}")
+ @current_resource.version($2)
+ end
+ end
+ end
+
+ unless status.exitstatus == 0 || status.exitstatus == 1
+ raise Chef::Exceptions::Package, "pacman failed - #{status.inspect}!"
+ end
+
+ @current_resource
+ end
+
+ def candidate_version
+ return @candidate_version if @candidate_version
+
+ repos = ["extra","core","community"]
+
+ if(::File.exists?("/etc/pacman.conf"))
+ pacman = ::File.read("/etc/pacman.conf")
+ repos = pacman.scan(/\[(.+)\]/).flatten
+ end
+
+ package_repos = repos.map {|r| Regexp.escape(r) }.join('|')
+
+ status = popen4("pacman -Ss #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ case line
+ when /^(#{package_repos})\/#{Regexp.escape(@new_resource.package_name)} (.+)$/
+ # $2 contains a string like "4.4.0-1 (kde kdenetwork)" or "3.10-4 (base)"
+ # simply split by space and use first token
+ @candidate_version = $2.split(" ").first
+ end
+ end
+ end
+
+ unless status.exitstatus == 0 || status.exitstatus == 1
+ raise Chef::Exceptions::Package, "pacman failed - #{status.inspect}!"
+ end
+
+ unless @candidate_version
+ raise Chef::Exceptions::Package, "pacman does not have a version of package #{@new_resource.package_name}"
+ end
+
+ @candidate_version
+
+ end
+
+ def install_package(name, version)
+ run_command_with_systems_locale(
+ :command => "pacman --sync --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}"
+ )
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ run_command_with_systems_locale(
+ :command => "pacman --remove --noconfirm --noprogressbar#{expand_options(@new_resource.options)} #{name}"
+ )
+ end
+
+ def purge_package(name, version)
+ remove_package(name, version)
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/portage.rb b/lib/chef/provider/package/portage.rb
new file mode 100644
index 0000000000..eb13e9855a
--- /dev/null
+++ b/lib/chef/provider/package/portage.rb
@@ -0,0 +1,138 @@
+#
+# Author:: Ezra Zygmuntowicz (<ezra@engineyard.com>)
+# Copyright:: Copyright (c) 2008 Opscode, 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 'chef/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+
+class Chef
+ class Provider
+ class Package
+ class Portage < Chef::Provider::Package
+ PACKAGE_NAME_PATTERN = %r{(?:([^/]+)/)?([^/]+)}
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+
+ @current_resource.version(nil)
+
+ category, pkg = %r{^#{PACKAGE_NAME_PATTERN}$}.match(@new_resource.package_name)[1,2]
+
+ possibilities = Dir["/var/db/pkg/#{category || "*"}/#{pkg}-*"].map {|d| d.sub(%r{/var/db/pkg/}, "") }
+ versions = possibilities.map do |entry|
+ if(entry =~ %r{[^/]+/#{Regexp.escape(pkg)}\-(\d[\.\d]*((_(alpha|beta|pre|rc|p)\d*)*)?(-r\d+)?)})
+ [$&, $1]
+ end
+ end.compact
+
+ if versions.size > 1
+ atoms = versions.map {|v| v.first }.sort
+ categories = atoms.map {|v| v.split('/')[0] }.uniq
+ if !category && categories.size > 1
+ raise Chef::Exceptions::Package, "Multiple packages found for #{@new_resource.package_name}: #{atoms.join(" ")}. Specify a category."
+ end
+ elsif versions.size == 1
+ @current_resource.version(versions.first.last)
+ Chef::Log.debug("#{@new_resource} current version #{$1}")
+ end
+
+ @current_resource
+ end
+
+
+ def parse_emerge(package, txt)
+ availables = {}
+ package_without_category = package.split("/").last
+ found_package_name = nil
+
+ txt.each_line do |line|
+ if line =~ /\*\s+#{PACKAGE_NAME_PATTERN}/
+ found_package_name = $&.strip
+ if found_package_name == package || found_package_name.split("/").last == package_without_category
+ availables[found_package_name] = nil
+ end
+ end
+
+ if line =~ /Latest version available: (.*)/ && availables.has_key?(found_package_name)
+ availables[found_package_name] = $1.strip
+ end
+ end
+
+ if availables.size > 1
+ # shouldn't happen if a category is specified so just use `package`
+ raise Chef::Exceptions::Package, "Multiple emerge results found for #{package}: #{availables.keys.join(" ")}. Specify a category."
+ end
+
+ availables.values.first
+ end
+
+ def candidate_version
+ return @candidate_version if @candidate_version
+
+ status = popen4("emerge --color n --nospinner --search #{@new_resource.package_name.split('/').last}") do |pid, stdin, stdout, stderr|
+ available, installed = parse_emerge(@new_resource.package_name, stdout.read)
+ @candidate_version = available
+ end
+
+ unless status.exitstatus == 0
+ raise Chef::Exceptions::Package, "emerge --search failed - #{status.inspect}!"
+ end
+
+ @candidate_version
+
+ end
+
+
+ def install_package(name, version)
+ pkg = "=#{name}-#{version}"
+
+ if(version =~ /^\~(.+)/)
+ # If we start with a tilde
+ pkg = "~#{name}-#{$1}"
+ end
+
+ run_command_with_systems_locale(
+ :command => "emerge -g --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}"
+ )
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ if(version)
+ pkg = "=#{@new_resource.package_name}-#{version}"
+ else
+ pkg = "#{@new_resource.package_name}"
+ end
+
+ run_command_with_systems_locale(
+ :command => "emerge --unmerge --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}"
+ )
+ end
+
+ def purge_package(name, version)
+ remove_package(name, version)
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/rpm.rb b/lib/chef/provider/package/rpm.rb
new file mode 100644
index 0000000000..033ce8efb9
--- /dev/null
+++ b/lib/chef/provider/package/rpm.rb
@@ -0,0 +1,121 @@
+#
+# Author:: Joshua Timberman (<joshua@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, 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 'chef/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'chef/mixin/get_source_from_package'
+
+class Chef
+ class Provider
+ class Package
+ class Rpm < Chef::Provider::Package
+
+ include Chef::Mixin::GetSourceFromPackage
+
+ def define_resource_requirements
+ super
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @package_source_exists }
+ a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
+ a.whyrun "Assuming package #{@new_resource.name} would have been made available."
+ end
+ requirements.assert(:all_actions) do |a|
+ a.assertion { !@rpm_status.nil? && (@rpm_status.exitstatus == 0 || @rpm_status.exitstatus == 1) }
+ a.failure_message Chef::Exceptions::Package, "Unable to determine current version due to RPM failure. Detail: #{@rpm_status.inspect}"
+ a.whyrun "Assuming current version would have been determined for package#{@new_resource.name}."
+ end
+ end
+
+ def load_current_resource
+ @package_source_provided = true
+ @package_source_exists = true
+
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+ @new_resource.version(nil)
+
+ if @new_resource.source
+ unless ::File.exists?(@new_resource.source)
+ @package_source_exists = false
+ return
+ end
+
+ Chef::Log.debug("#{@new_resource} checking rpm status")
+ status = popen4("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}") do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ case line
+ when /([\w\d_.-]+)\s([\w\d_.-]+)/
+ @current_resource.package_name($1)
+ @new_resource.version($2)
+ end
+ end
+ end
+ else
+ if Array(@new_resource.action).include?(:install)
+ @package_source_exists = false
+ return
+ end
+ end
+
+ Chef::Log.debug("#{@new_resource} checking install state")
+ @rpm_status = popen4("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@current_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ case line
+ when /([\w\d_.-]+)\s([\w\d_.-]+)/
+ Chef::Log.debug("#{@new_resource} current version is #{$2}")
+ @current_resource.version($2)
+ end
+ end
+ end
+
+
+ @current_resource
+ end
+
+ def install_package(name, version)
+ unless @current_resource.version
+ run_command_with_systems_locale(
+ :command => "rpm #{@new_resource.options} -i #{@new_resource.source}"
+ )
+ else
+ run_command_with_systems_locale(
+ :command => "rpm #{@new_resource.options} -U #{@new_resource.source}"
+ )
+ end
+ end
+
+ alias_method :upgrade_package, :install_package
+
+ def remove_package(name, version)
+ if version
+ run_command_with_systems_locale(
+ :command => "rpm #{@new_resource.options} -e #{name}-#{version}"
+ )
+ else
+ run_command_with_systems_locale(
+ :command => "rpm #{@new_resource.options} -e #{name}"
+ )
+ end
+ end
+
+ end
+ end
+ end
+end
+
diff --git a/lib/chef/provider/package/rubygems.rb b/lib/chef/provider/package/rubygems.rb
new file mode 100644
index 0000000000..e60d73ab62
--- /dev/null
+++ b/lib/chef/provider/package/rubygems.rb
@@ -0,0 +1,548 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Author:: Daniel DeLeo (<dan@opscode.com>)
+# Copyright:: Copyright (c) 2008, 2010 Opscode, 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 'chef/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'chef/mixin/get_source_from_package'
+
+# Class methods on Gem are defined in rubygems
+require 'rubygems'
+# Ruby 1.9's gem_prelude can interact poorly with loading the full rubygems
+# explicitly like this. Make sure rubygems/specification is always last in this
+# list
+require 'rubygems/version'
+require 'rubygems/dependency'
+require 'rubygems/spec_fetcher'
+require 'rubygems/platform'
+require 'rubygems/format'
+require 'rubygems/dependency_installer'
+require 'rubygems/uninstaller'
+require 'rubygems/specification'
+
+class Chef
+ class Provider
+ class Package
+ class Rubygems < Chef::Provider::Package
+ class GemEnvironment
+ # HACK: trigger gem config load early. Otherwise it can get lazy
+ # loaded during operations where we've set Gem.sources to an
+ # alternate value and overwrite it with the defaults.
+ Gem.configuration
+
+ DEFAULT_UNINSTALLER_OPTS = {:ignore => true, :executables => true}
+
+ ##
+ # The paths where rubygems should search for installed gems.
+ # Implemented by subclasses.
+ def gem_paths
+ raise NotImplementedError
+ end
+
+ ##
+ # A rubygems source index containing the list of gemspecs for all
+ # available gems in the gem installation.
+ # Implemented by subclasses
+ # === Returns
+ # Gem::SourceIndex
+ def gem_source_index
+ raise NotImplementedError
+ end
+
+ ##
+ # A rubygems specification object containing the list of gemspecs for all
+ # available gems in the gem installation.
+ # Implemented by subclasses
+ # For rubygems >= 1.8.0
+ # === Returns
+ # Gem::Specification
+ def gem_specification
+ raise NotImplementedError
+ end
+
+ ##
+ # Lists the installed versions of +gem_name+, constrained by the
+ # version spec in +gem_dep+
+ # === Arguments
+ # Gem::Dependency +gem_dep+ is a Gem::Dependency object, its version
+ # specification constrains which gems are returned.
+ # === Returns
+ # [Gem::Specification] an array of Gem::Specification objects
+ def installed_versions(gem_dep)
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.8.0')
+ gem_specification.find_all_by_name(gem_dep.name, gem_dep.requirement)
+ else
+ gem_source_index.search(gem_dep)
+ end
+ end
+
+ ##
+ # Yields to the provided block with rubygems' source list set to the
+ # list provided. Always resets the list when the block returns or
+ # raises an exception.
+ def with_gem_sources(*sources)
+ sources.compact!
+ original_sources = Gem.sources
+ Gem.sources = sources unless sources.empty?
+ yield
+ ensure
+ Gem.sources = original_sources
+ end
+
+ ##
+ # Determines the candidate version for a gem from a .gem file on disk
+ # and checks if it matches the version contraints in +gem_dependency+
+ # === Returns
+ # Gem::Version a singular gem version object is returned if the gem
+ # is available
+ # nil returns nil if the gem on disk doesn't match the
+ # version constraints for +gem_dependency+
+ def candidate_version_from_file(gem_dependency, source)
+ spec = Gem::Format.from_file_by_path(source).spec
+ if spec.satisfies_requirement?(gem_dependency)
+ logger.debug {"#{@new_resource} found candidate gem version #{spec.version} from local gem package #{source}"}
+ spec.version
+ else
+ # This is probably going to end badly...
+ logger.warn { "#{@new_resource} gem package #{source} does not satisfy the requirements #{gem_dependency.to_s}" }
+ nil
+ end
+ end
+
+ ##
+ # Finds the newest version that satisfies the constraints of
+ # +gem_dependency+. The version is determined from the cache or a
+ # round-trip to the server as needed. The architecture and gem
+ # sources will be set before making the query.
+ # === Returns
+ # Gem::Version a singular gem version object is returned if the gem
+ # is available
+ # nil returns nil if the gem could not be found
+ def candidate_version_from_remote(gem_dependency, *sources)
+ raise NotImplementedError
+ end
+
+ ##
+ # Find the newest gem version available from Gem.sources that satisfies
+ # the constraints of +gem_dependency+
+ def find_newest_remote_version(gem_dependency, *sources)
+ # DependencyInstaller sorts the results such that the last one is
+ # always the one it considers best.
+ spec_with_source = dependency_installer.find_gems_with_sources(gem_dependency).last
+
+ spec = spec_with_source && spec_with_source[0]
+ version = spec && spec_with_source[0].version
+ if version
+ logger.debug { "#{@new_resource} found gem #{spec.name} version #{version} for platform #{spec.platform} from #{spec_with_source[1]}" }
+ version
+ else
+ source_list = sources.compact.empty? ? "[#{Gem.sources.join(', ')}]" : "[#{sources.join(', ')}]"
+ logger.warn { "#{@new_resource} failed to find gem #{gem_dependency} from #{source_list}" }
+ nil
+ end
+ end
+
+ ##
+ # Installs a gem via the rubygems ruby API.
+ # === Options
+ # :sources rubygems servers to use
+ # Other options are passed to Gem::DependencyInstaller.new
+ def install(gem_dependency, options={})
+ with_gem_sources(*options.delete(:sources)) do
+ with_correct_verbosity do
+ dependency_installer(options).install(gem_dependency)
+ end
+ end
+ end
+
+ ##
+ # Uninstall the gem +gem_name+ via the rubygems ruby API. If
+ # +gem_version+ is provided, only that version will be uninstalled.
+ # Otherwise, all versions are uninstalled.
+ # === Options
+ # Options are passed to Gem::Uninstaller.new
+ def uninstall(gem_name, gem_version=nil, opts={})
+ gem_version ? opts[:version] = gem_version : opts[:all] = true
+ with_correct_verbosity do
+ uninstaller(gem_name, opts).uninstall
+ end
+ end
+
+ ##
+ # Set rubygems' user interaction to ConsoleUI or SilentUI depending
+ # on our current debug level
+ def with_correct_verbosity
+ Gem::DefaultUserInteraction.ui = Chef::Log.debug? ? Gem::ConsoleUI.new : Gem::SilentUI.new
+ yield
+ end
+
+ def dependency_installer(opts={})
+ Gem::DependencyInstaller.new(opts)
+ end
+
+ def uninstaller(gem_name, opts={})
+ Gem::Uninstaller.new(gem_name, DEFAULT_UNINSTALLER_OPTS.merge(opts))
+ end
+
+ private
+
+ def logger
+ Chef::Log.logger
+ end
+
+ end
+
+ class CurrentGemEnvironment < GemEnvironment
+
+ def gem_paths
+ Gem.path
+ end
+
+ def gem_source_index
+ Gem.source_index
+ end
+
+ def gem_specification
+ Gem::Specification
+ end
+
+ def candidate_version_from_remote(gem_dependency, *sources)
+ with_gem_sources(*sources) do
+ find_newest_remote_version(gem_dependency, *sources)
+ end
+ end
+
+ end
+
+ class AlternateGemEnvironment < GemEnvironment
+ JRUBY_PLATFORM = /(:?universal|x86_64|x86)\-java\-[0-9\.]+/
+
+ def self.gempath_cache
+ @gempath_cache ||= {}
+ end
+
+ def self.platform_cache
+ @platform_cache ||= {}
+ end
+
+ include Chef::Mixin::ShellOut
+
+ attr_reader :gem_binary_location
+
+ def initialize(gem_binary_location)
+ @gem_binary_location = gem_binary_location
+ end
+
+ def gem_paths
+ if self.class.gempath_cache.key?(@gem_binary_location)
+ self.class.gempath_cache[@gem_binary_location]
+ else
+ # shellout! is a fork/exec which won't work on windows
+ shell_style_paths = shell_out!("#{@gem_binary_location} env gempath").stdout
+ # on windows, the path separator is (usually? always?) semicolon
+ paths = shell_style_paths.split(::File::PATH_SEPARATOR).map { |path| path.strip }
+ self.class.gempath_cache[@gem_binary_location] = paths
+ end
+ end
+
+ def gem_source_index
+ @source_index ||= Gem::SourceIndex.from_gems_in(*gem_paths.map { |p| p + '/specifications' })
+ end
+
+ def gem_specification
+ # Only once, dirs calls a reset
+ unless @specification
+ Gem::Specification.dirs = gem_paths
+ @specification = Gem::Specification
+ end
+ @specification
+ end
+
+ ##
+ # Attempt to detect the correct platform settings for the target gem
+ # environment.
+ #
+ # In practice, this only makes a difference if different versions are
+ # available depending on platform, and only if the target gem
+ # environment has a radically different platform (i.e., jruby), so we
+ # just try to detect jruby and fall back to the current platforms
+ # (Gem.platforms) if we don't detect it.
+ #
+ # === Returns
+ # [String|Gem::Platform] returns an array of Gem::Platform-compatible
+ # objects, i.e., Strings that are valid for Gem::Platform or actual
+ # Gem::Platform objects.
+ def gem_platforms
+ if self.class.platform_cache.key?(@gem_binary_location)
+ self.class.platform_cache[@gem_binary_location]
+ else
+ gem_environment = shell_out!("#{@gem_binary_location} env").stdout
+ if jruby = gem_environment[JRUBY_PLATFORM]
+ self.class.platform_cache[@gem_binary_location] = ['ruby', Gem::Platform.new(jruby)]
+ else
+ self.class.platform_cache[@gem_binary_location] = Gem.platforms
+ end
+ end
+ end
+
+ def with_gem_platforms(*alt_gem_platforms)
+ alt_gem_platforms.flatten!
+ original_gem_platforms = Gem.platforms
+ Gem.platforms = alt_gem_platforms
+ yield
+ ensure
+ Gem.platforms = original_gem_platforms
+ end
+
+ def candidate_version_from_remote(gem_dependency, *sources)
+ with_gem_sources(*sources) do
+ with_gem_platforms(*gem_platforms) do
+ find_newest_remote_version(gem_dependency, *sources)
+ end
+ end
+ end
+
+ end
+
+ include Chef::Mixin::ShellOut
+
+ attr_reader :gem_env
+ attr_reader :cleanup_gem_env
+
+ def logger
+ Chef::Log.logger
+ end
+
+ include Chef::Mixin::GetSourceFromPackage
+
+ def initialize(new_resource, run_context=nil)
+ super
+ @cleanup_gem_env = true
+ if new_resource.gem_binary
+ if new_resource.options && new_resource.options.kind_of?(Hash)
+ msg = "options cannot be given as a hash when using an explicit gem_binary\n"
+ msg << "in #{new_resource} from #{new_resource.source_line}"
+ raise ArgumentError, msg
+ end
+ @gem_env = AlternateGemEnvironment.new(new_resource.gem_binary)
+ Chef::Log.debug("#{@new_resource} using gem '#{new_resource.gem_binary}'")
+ elsif is_omnibus? && (!@new_resource.instance_of? Chef::Resource::ChefGem)
+ # Opscode Omnibus - The ruby that ships inside omnibus is only used for Chef
+ # Default to installing somewhere more functional
+ if new_resource.options && new_resource.options.kind_of?(Hash)
+ msg = "options should be a string instead of a hash\n"
+ msg << "in #{new_resource} from #{new_resource.source_line}"
+ raise ArgumentError, msg
+ end
+ gem_location = find_gem_by_path
+ @new_resource.gem_binary gem_location
+ @gem_env = AlternateGemEnvironment.new(gem_location)
+ Chef::Log.debug("#{@new_resource} using gem '#{gem_location}'")
+ else
+ @gem_env = CurrentGemEnvironment.new
+ @cleanup_gem_env = false
+ Chef::Log.debug("#{@new_resource} using gem from running ruby environment")
+ end
+ end
+
+ def is_omnibus?
+ if RbConfig::CONFIG['bindir'] =~ %r!/opt/(opscode|chef)/embedded/bin!
+ Chef::Log.debug("#{@new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}")
+ # Omnibus installs to a static path because of linking on unix, find it.
+ true
+ elsif RbConfig::CONFIG['bindir'].sub(/^[\w]:/, '') == "/opscode/chef/embedded/bin"
+ Chef::Log.debug("#{@new_resource} detected omnibus installation in #{RbConfig::CONFIG['bindir']}")
+ # windows, with the drive letter removed
+ true
+ else
+ false
+ end
+ end
+
+ def find_gem_by_path
+ Chef::Log.debug("#{@new_resource} searching for 'gem' binary in path: #{ENV['PATH']}")
+ separator = ::File::ALT_SEPARATOR ? ::File::ALT_SEPARATOR : ::File::SEPARATOR
+ path_to_first_gem = ENV['PATH'].split(::File::PATH_SEPARATOR).select { |path| ::File.exists?(path + separator + "gem") }.first
+ raise Chef::Exceptions::FileNotFound, "Unable to find 'gem' binary in path: #{ENV['PATH']}" if path_to_first_gem.nil?
+ path_to_first_gem + separator + "gem"
+ end
+
+ def gem_dependency
+ Gem::Dependency.new(@new_resource.package_name, @new_resource.version)
+ end
+
+ def source_is_remote?
+ return true if @new_resource.source.nil?
+ scheme = URI.parse(@new_resource.source).scheme
+ # URI.parse gets confused by MS Windows paths with forward slashes.
+ scheme = nil if scheme =~ /^[a-z]$/
+ %w{http https}.include?(scheme)
+ end
+
+ def current_version
+ #raise 'todo'
+ # If one or more matching versions are installed, the newest of them
+ # is the current version
+ if !matching_installed_versions.empty?
+ gemspec = matching_installed_versions.last
+ logger.debug { "#{@new_resource} found installed gem #{gemspec.name} version #{gemspec.version} matching #{gem_dependency}"}
+ gemspec
+ # If no version matching the requirements exists, the latest installed
+ # version is the current version.
+ elsif !all_installed_versions.empty?
+ gemspec = all_installed_versions.last
+ logger.debug { "#{@new_resource} newest installed version of gem #{gemspec.name} is #{gemspec.version}" }
+ gemspec
+ else
+ logger.debug { "#{@new_resource} no installed version found for #{gem_dependency.to_s}"}
+ nil
+ end
+ end
+
+ def matching_installed_versions
+ @matching_installed_versions ||= @gem_env.installed_versions(gem_dependency)
+ end
+
+ def all_installed_versions
+ @all_installed_versions ||= begin
+ @gem_env.installed_versions(Gem::Dependency.new(gem_dependency.name, '>= 0'))
+ end
+ end
+
+ def gem_sources
+ @new_resource.source ? Array(@new_resource.source) : nil
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package::GemPackage.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+ if current_spec = current_version
+ @current_resource.version(current_spec.version.to_s)
+ end
+ @current_resource
+ end
+
+ def cleanup_after_converge
+ if @cleanup_gem_env
+ logger.debug { "#{@new_resource} resetting gem environment to default" }
+ Gem.clear_paths
+ end
+ end
+
+ def candidate_version
+ @candidate_version ||= begin
+ if target_version_already_installed?
+ nil
+ elsif source_is_remote?
+ @gem_env.candidate_version_from_remote(gem_dependency, *gem_sources).to_s
+ else
+ @gem_env.candidate_version_from_file(gem_dependency, @new_resource.source).to_s
+ end
+ end
+ end
+
+ def target_version_already_installed?
+ return false unless @current_resource && @current_resource.version
+ return false if @current_resource.version.nil?
+
+ Gem::Requirement.new(@new_resource.version).satisfied_by?(Gem::Version.new(@current_resource.version))
+ end
+
+ ##
+ # Installs the gem, using either the gems API or shelling out to `gem`
+ # according to the following criteria:
+ # 1. Use gems API (Gem::DependencyInstaller) by default
+ # 2. shell out to `gem install` when a String of options is given
+ # 3. use gems API with options if a hash of options is given
+ def install_package(name, version)
+ if source_is_remote? && @new_resource.gem_binary.nil?
+ if @new_resource.options.nil?
+ @gem_env.install(gem_dependency, :sources => gem_sources)
+ elsif @new_resource.options.kind_of?(Hash)
+ options = @new_resource.options
+ options[:sources] = gem_sources
+ @gem_env.install(gem_dependency, options)
+ else
+ install_via_gem_command(name, version)
+ end
+ elsif @new_resource.gem_binary.nil?
+ @gem_env.install(@new_resource.source)
+ else
+ install_via_gem_command(name,version)
+ end
+ true
+ end
+
+ def gem_binary_path
+ @new_resource.gem_binary || 'gem'
+ end
+
+ def install_via_gem_command(name, version)
+ if @new_resource.source =~ /\.gem$/i
+ name = @new_resource.source
+ else
+ src = @new_resource.source && " --source=#{@new_resource.source} --source=http://rubygems.org"
+ end
+ if version
+ shell_out!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}", :env=>nil)
+ else
+ shell_out!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri #{src}#{opts}", :env=>nil)
+ end
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ if @new_resource.gem_binary.nil?
+ if @new_resource.options.nil?
+ @gem_env.uninstall(name, version)
+ elsif @new_resource.options.kind_of?(Hash)
+ @gem_env.uninstall(name, version, @new_resource.options)
+ else
+ uninstall_via_gem_command(name, version)
+ end
+ else
+ uninstall_via_gem_command(name, version)
+ end
+ end
+
+ def uninstall_via_gem_command(name, version)
+ if version
+ shell_out!("#{gem_binary_path} uninstall #{name} -q -x -I -v \"#{version}\"#{opts}", :env=>nil)
+ else
+ shell_out!("#{gem_binary_path} uninstall #{name} -q -x -I -a#{opts}", :env=>nil)
+ end
+ end
+
+ def purge_package(name, version)
+ remove_package(name, version)
+ end
+
+ private
+
+ def opts
+ expand_options(@new_resource.options)
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/smartos.rb b/lib/chef/provider/package/smartos.rb
new file mode 100644
index 0000000000..a3ef1e5e86
--- /dev/null
+++ b/lib/chef/provider/package/smartos.rb
@@ -0,0 +1,84 @@
+#
+# Authors:: Trevor O (trevoro@joyent.com)
+# Bryan McLellan (btm@loftninjas.org)
+# Matthew Landauer (matthew@openaustralia.org)
+# Copyright:: Copyright (c) 2009 Bryan McLellan, Matthew Landauer
+# 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.
+#
+# Notes
+#
+# * Supports installing using a local package name
+# * Otherwise reverts to installing from the pkgsrc repositories URL
+
+require 'chef/provider/package'
+require 'chef/mixin/shell_out'
+require 'chef/resource/package'
+require 'chef/mixin/get_source_from_package'
+
+class Chef
+ class Provider
+ class Package
+ class SmartOS < Chef::Provider::Package
+ include Chef::Mixin::ShellOut
+ attr_accessor :is_virtual_package
+
+
+ def load_current_resource
+ Chef::Log.debug("#{@new_resource} loading current resource")
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+ @current_resource.version(nil)
+ check_package_state(@new_resource.package_name)
+ @current_resource # modified by check_package_state
+ end
+
+ def check_package_state(name)
+ Chef::Log.debug("#{@new_resource} checking package #{name}")
+ # XXX
+ version = nil
+ info = shell_out!("pkg_info -E \"#{name}*\"", :env => nil, :returns => [0,1])
+
+ if info.stdout
+ version = info.stdout[/^#{@new_resource.package_name}-(.+)/, 1]
+ end
+
+ if !version
+ @current_resource.version(nil)
+ else
+ @current_resource.version(version)
+ end
+ end
+
+ def install_package(name, version)
+ Chef::Log.debug("#{@new_resource} installing package #{name}-#{version}")
+ package = "#{name}-#{version}"
+ out = shell_out!("pkgin -y install #{package}", :env => nil)
+ end
+
+ def upgrade_package(name, version)
+ Chef::Log.debug("#{@new_resource} upgrading package #{name}-#{version}")
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ Chef::Log.debug("#{@new_resource} removing package #{name}-#{version}")
+ package = "#{name}-#{version}"
+ out = shell_out!("pkgin -y remove #{package}", :env => nil)
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/solaris.rb b/lib/chef/provider/package/solaris.rb
new file mode 100644
index 0000000000..f502a0dc96
--- /dev/null
+++ b/lib/chef/provider/package/solaris.rb
@@ -0,0 +1,139 @@
+#
+# Author:: Toomas Pelberg (<toomasp@gmx.net>)
+# Copyright:: Copyright (c) 2010 Opscode, 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 'chef/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'chef/mixin/get_source_from_package'
+
+class Chef
+ class Provider
+ class Package
+ class Solaris < Chef::Provider::Package
+
+ include Chef::Mixin::GetSourceFromPackage
+
+ # def initialize(*args)
+ # super
+ # @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ # end
+ def define_resource_requirements
+ super
+ requirements.assert(:install) do |a|
+ a.assertion { @new_resource.source }
+ a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
+ end
+ requirements.assert(:all_actions) do |a|
+ a.assertion { !@new_resource.source || @package_source_found }
+ a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
+ a.whyrun "would assume #{@new_resource.source} would be have previously been made available"
+ end
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+ @new_resource.version(nil)
+
+ if @new_resource.source
+ @package_source_found = ::File.exists?(@new_resource.source)
+ if @package_source_found
+ Chef::Log.debug("#{@new_resource} checking pkg status")
+ status = popen4("pkginfo -l -d #{@new_resource.source} #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ case line
+ when /VERSION:\s+(.+)/
+ @new_resource.version($1)
+ end
+ end
+ end
+ end
+ end
+
+ Chef::Log.debug("#{@new_resource} checking install state")
+ status = popen4("pkginfo -l #{@current_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ case line
+ when /VERSION:\s+(.+)/
+ Chef::Log.debug("#{@new_resource} version #{$1} is already installed")
+ @current_resource.version($1)
+ end
+ end
+ end
+
+ unless status.exitstatus == 0 || status.exitstatus == 1
+ raise Chef::Exceptions::Package, "pkginfo failed - #{status.inspect}!"
+ end
+
+ unless @current_resource.version.nil?
+ @current_resource.version(nil)
+ end
+
+ @current_resource
+ end
+
+ def candidate_version
+ return @candidate_version if @candidate_version
+ status = popen4("pkginfo -l -d #{@new_resource.source} #{new_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdout.each_line do |line|
+ case line
+ when /VERSION:\s+(.+)/
+ @candidate_version = $1
+ @new_resource.version($1)
+ Chef::Log.debug("#{@new_resource} setting install candidate version to #{@candidate_version}")
+ end
+ end
+ end
+ unless status.exitstatus == 0
+ raise Chef::Exceptions::Package, "pkginfo -l -d #{@new_resource.source} - #{status.inspect}!"
+ end
+ @candidate_version
+ end
+
+ def install_package(name, version)
+ Chef::Log.debug("#{@new_resource} package install options: #{@new_resource.options}")
+ if @new_resource.options.nil?
+ run_command_with_systems_locale(
+ :command => "pkgadd -n -d #{@new_resource.source} all"
+ )
+ Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
+ else
+ run_command_with_systems_locale(
+ :command => "pkgadd -n#{expand_options(@new_resource.options)} -d #{@new_resource.source} all"
+ )
+ Chef::Log.debug("#{@new_resource} installed version #{@new_resource.version} from: #{@new_resource.source}")
+ end
+ end
+
+ def remove_package(name, version)
+ if @new_resource.options.nil?
+ run_command_with_systems_locale(
+ :command => "pkgrm -n #{name}"
+ )
+ Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
+ else
+ run_command_with_systems_locale(
+ :command => "pkgrm -n#{expand_options(@new_resource.options)} #{name}"
+ )
+ Chef::Log.debug("#{@new_resource} removed version #{@new_resource.version}")
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/yum-dump.py b/lib/chef/provider/package/yum-dump.py
new file mode 100644
index 0000000000..99136eceec
--- /dev/null
+++ b/lib/chef/provider/package/yum-dump.py
@@ -0,0 +1,287 @@
+#
+# Author:: Matthew Kent (<mkent@magoazul.com>)
+# Copyright:: Copyright (c) 2009, 2011 Matthew Kent
+# 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.
+#
+
+# yum-dump.py
+# Inspired by yumhelper.py by David Lutterkort
+#
+# Produce a list of installed, available and re-installable packages using yum
+# and dump the results to stdout.
+#
+# yum-dump invokes yum similarly to the command line interface which makes it
+# subject to most of the configuration paramaters in yum.conf. yum-dump will
+# also load yum plugins in the same manor as yum - these can affect the output.
+#
+# Can be run as non root, but that won't update the cache.
+#
+# Intended to support yum 2.x and 3.x
+
+import os
+import sys
+import time
+import yum
+import re
+import errno
+
+from yum import Errors
+from optparse import OptionParser
+from distutils import version
+
+YUM_PID_FILE='/var/run/yum.pid'
+
+# Seconds to wait for exclusive access to yum
+LOCK_TIMEOUT = 10
+
+YUM_VER = version.StrictVersion(yum.__version__)
+YUM_MAJOR = YUM_VER.version[0]
+
+if YUM_MAJOR > 3 or YUM_MAJOR < 2:
+ print >> sys.stderr, "yum-dump Error: Can't match supported yum version" \
+ " (%s)" % yum.__version__
+ sys.exit(1)
+
+# Required for Provides output
+if YUM_MAJOR == 2:
+ import rpm
+ import rpmUtils.miscutils
+
+def setup(yb, options):
+ # Only want our output
+ #
+ if YUM_MAJOR == 3:
+ try:
+ if YUM_VER >= version.StrictVersion("3.2.22"):
+ yb.preconf.errorlevel=0
+ yb.preconf.debuglevel=0
+
+ # initialize the config
+ yb.conf
+ else:
+ yb.doConfigSetup(errorlevel=0, debuglevel=0)
+ except yum.Errors.ConfigError, e:
+ # supresses an ignored exception at exit
+ yb.preconf = None
+ print >> sys.stderr, "yum-dump Config Error: %s" % e
+ return 1
+ except ValueError, e:
+ yb.preconf = None
+ print >> sys.stderr, "yum-dump Options Error: %s" % e
+ return 1
+ elif YUM_MAJOR == 2:
+ yb.doConfigSetup()
+
+ def __log(a,b): pass
+
+ yb.log = __log
+ yb.errorlog = __log
+
+ # Give Chef every possible package version, it can decide what to do with them
+ if YUM_MAJOR == 3:
+ yb.conf.showdupesfromrepos = True
+ elif YUM_MAJOR == 2:
+ yb.conf.setConfigOption('showdupesfromrepos', True)
+
+ # Optionally run only on cached repositories, but non root must use the cache
+ if os.geteuid() != 0:
+ if YUM_MAJOR == 3:
+ yb.conf.cache = True
+ elif YUM_MAJOR == 2:
+ yb.conf.setConfigOption('cache', True)
+ else:
+ if YUM_MAJOR == 3:
+ yb.conf.cache = options.cache
+ elif YUM_MAJOR == 2:
+ yb.conf.setConfigOption('cache', options.cache)
+
+ return 0
+
+def dump_packages(yb, list, output_provides):
+ packages = {}
+
+ if YUM_MAJOR == 2:
+ yb.doTsSetup()
+ yb.doRepoSetup()
+ yb.doSackSetup()
+
+ db = yb.doPackageLists(list)
+
+ for pkg in db.installed:
+ pkg.type = 'i'
+ packages[str(pkg)] = pkg
+
+ if YUM_VER >= version.StrictVersion("3.2.21"):
+ for pkg in db.available:
+ pkg.type = 'a'
+ packages[str(pkg)] = pkg
+
+ # These are both installed and available
+ for pkg in db.reinstall_available:
+ pkg.type = 'r'
+ packages[str(pkg)] = pkg
+ else:
+ # Old style method - no reinstall list
+ for pkg in yb.pkgSack.returnPackages():
+
+ if str(pkg) in packages:
+ if packages[str(pkg)].type == "i":
+ packages[str(pkg)].type = 'r'
+ continue
+
+ pkg.type = 'a'
+ packages[str(pkg)] = pkg
+
+ unique_packages = packages.values()
+
+ unique_packages.sort(lambda x, y: cmp(x.name, y.name))
+
+ for pkg in unique_packages:
+ if output_provides == "all" or \
+ (output_provides == "installed" and (pkg.type == "i" or pkg.type == "r")):
+
+ # yum 2 doesn't have provides_print, implement it ourselves using methods
+ # based on requires gathering in packages.py
+ if YUM_MAJOR == 2:
+ provlist = []
+
+ # Installed and available are gathered in different ways
+ if pkg.type == 'i' or pkg.type == 'r':
+ names = pkg.hdr[rpm.RPMTAG_PROVIDENAME]
+ flags = pkg.hdr[rpm.RPMTAG_PROVIDEFLAGS]
+ ver = pkg.hdr[rpm.RPMTAG_PROVIDEVERSION]
+ if names is not None:
+ tmplst = zip(names, flags, ver)
+
+ for (n, f, v) in tmplst:
+ prov = rpmUtils.miscutils.formatRequire(n, v, f)
+ provlist.append(prov)
+ # This is slow :(
+ elif pkg.type == 'a':
+ for prcoTuple in pkg.returnPrco('provides'):
+ prcostr = pkg.prcoPrintable(prcoTuple)
+ provlist.append(prcostr)
+
+ provides = provlist
+ else:
+ provides = pkg.provides_print
+ else:
+ provides = "[]"
+
+ print '%s %s %s %s %s %s %s %s' % (
+ pkg.name,
+ pkg.epoch,
+ pkg.version,
+ pkg.release,
+ pkg.arch,
+ provides,
+ pkg.type,
+ pkg.repoid )
+
+ return 0
+
+def yum_dump(options):
+ lock_obtained = False
+
+ yb = yum.YumBase()
+
+ status = setup(yb, options)
+ if status != 0:
+ return status
+
+ if options.output_options:
+ print "[option installonlypkgs] %s" % " ".join(yb.conf.installonlypkgs)
+
+ # Non root can't handle locking on rhel/centos 4
+ if os.geteuid() != 0:
+ return dump_packages(yb, options.package_list, options.output_provides)
+
+ # Wrap the collection and output of packages in yum's global lock to prevent
+ # any inconsistencies.
+ try:
+ # Spin up to LOCK_TIMEOUT
+ countdown = LOCK_TIMEOUT
+ while True:
+ try:
+ yb.doLock(YUM_PID_FILE)
+ lock_obtained = True
+ except Errors.LockError, e:
+ time.sleep(1)
+ countdown -= 1
+ if countdown == 0:
+ print >> sys.stderr, "yum-dump Locking Error! Couldn't obtain an " \
+ "exclusive yum lock in %d seconds. Giving up." % LOCK_TIMEOUT
+ return 200
+ else:
+ break
+
+ return dump_packages(yb, options.package_list, options.output_provides)
+
+ # Ensure we clear the lock and cleanup any resources
+ finally:
+ try:
+ yb.closeRpmDB()
+ if lock_obtained == True:
+ yb.doUnlock(YUM_PID_FILE)
+ except Errors.LockError, e:
+ print >> sys.stderr, "yum-dump Unlock Error: %s" % e
+ return 200
+
+def main():
+ usage = "Usage: %prog [options]\n" + \
+ "Output a list of installed, available and re-installable packages via yum"
+ parser = OptionParser(usage=usage)
+ parser.add_option("-C", "--cache",
+ action="store_true", dest="cache", default=False,
+ help="run entirely from cache, don't update cache")
+ parser.add_option("-o", "--options",
+ action="store_true", dest="output_options", default=False,
+ help="output select yum options useful to Chef")
+ parser.add_option("-p", "--installed-provides",
+ action="store_const", const="installed", dest="output_provides", default="none",
+ help="output Provides for installed packages, big/wide output")
+ parser.add_option("-P", "--all-provides",
+ action="store_const", const="all", dest="output_provides", default="none",
+ help="output Provides for all package, slow, big/wide output")
+ parser.add_option("-i", "--installed",
+ action="store_const", const="installed", dest="package_list", default="all",
+ help="output only installed packages")
+ parser.add_option("-a", "--available",
+ action="store_const", const="available", dest="package_list", default="all",
+ help="output only available and re-installable packages")
+
+ (options, args) = parser.parse_args()
+
+ try:
+ return yum_dump(options)
+
+ except yum.Errors.RepoError, e:
+ print >> sys.stderr, "yum-dump Repository Error: %s" % e
+ return 1
+
+ except yum.Errors.YumBaseError, e:
+ print >> sys.stderr, "yum-dump General Error: %s" % e
+ return 1
+
+try:
+ status = main()
+# Suppress a nasty broken pipe error when output is piped to utilities like 'head'
+except IOError, e:
+ if e.errno == errno.EPIPE:
+ sys.exit(1)
+ else:
+ raise
+
+sys.exit(status)
diff --git a/lib/chef/provider/package/yum.rb b/lib/chef/provider/package/yum.rb
new file mode 100644
index 0000000000..9048048b83
--- /dev/null
+++ b/lib/chef/provider/package/yum.rb
@@ -0,0 +1,1214 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, 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 'chef/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'singleton'
+require 'chef/mixin/get_source_from_package'
+
+
+class Chef
+ class Provider
+ class Package
+ class Yum < Chef::Provider::Package
+
+ class RPMUtils
+ class << self
+
+ # RPM::Version version_parse equivalent
+ def version_parse(evr)
+ return if evr.nil?
+
+ epoch = nil
+ # assume this is a version
+ version = evr
+ release = nil
+
+ lead = 0
+ tail = evr.size
+
+ if evr =~ %r{^([\d]+):}
+ epoch = $1.to_i
+ lead = $1.length + 1
+ elsif evr[0].ord == ":".ord
+ epoch = 0
+ lead = 1
+ end
+
+ if evr =~ %r{:?.*-(.*)$}
+ release = $1
+ tail = evr.length - release.length - lead - 1
+
+ if release.empty?
+ release = nil
+ end
+ end
+
+ version = evr[lead,tail]
+ if version.empty?
+ version = nil
+ end
+
+ [ epoch, version, release ]
+ end
+
+ # verify
+ def isalnum(x)
+ isalpha(x) or isdigit(x)
+ end
+
+ def isalpha(x)
+ v = x.ord
+ (v >= 65 and v <= 90) or (v >= 97 and v <= 122)
+ end
+
+ def isdigit(x)
+ v = x.ord
+ v >= 48 and v <= 57
+ end
+
+ # based on the reference spec in lib/rpmvercmp.c in rpm 4.9.0
+ def rpmvercmp(x, y)
+ # easy! :)
+ return 0 if x == y
+
+ if x.nil?
+ x = ""
+ end
+
+ if y.nil?
+ y = ""
+ end
+
+ # not so easy :(
+ #
+ # takes 2 strings like
+ #
+ # x = "1.20.b18.el5"
+ # y = "1.20.b17.el5"
+ #
+ # breaks into purely alpha and numeric segments and compares them using
+ # some rules
+ #
+ # * 10 > 1
+ # * 1 > a
+ # * z > a
+ # * Z > A
+ # * z > Z
+ # * leading zeros are ignored
+ # * separators (periods, commas) are ignored
+ # * "1.20.b18.el5.extrastuff" > "1.20.b18.el5"
+
+ x_pos = 0 # overall string element reference position
+ x_pos_max = x.length - 1 # number of elements in string, starting from 0
+ x_seg_pos = 0 # segment string element reference position
+ x_comp = nil # segment to compare
+
+ y_pos = 0
+ y_seg_pos = 0
+ y_pos_max = y.length - 1
+ y_comp = nil
+
+ while (x_pos <= x_pos_max and y_pos <= y_pos_max)
+ # first we skip over anything non alphanumeric
+ while (x_pos <= x_pos_max) and (isalnum(x[x_pos]) == false)
+ x_pos += 1 # +1 over pos_max if end of string
+ end
+ while (y_pos <= y_pos_max) and (isalnum(y[y_pos]) == false)
+ y_pos += 1
+ end
+
+ # if we hit the end of either we are done matching segments
+ if (x_pos == x_pos_max + 1) or (y_pos == y_pos_max + 1)
+ break
+ end
+
+ # we are now at the start of a alpha or numeric segment
+ x_seg_pos = x_pos
+ y_seg_pos = y_pos
+
+ # grab segment so we can compare them
+ if isdigit(x[x_seg_pos].ord)
+ x_seg_is_num = true
+
+ # already know it's a digit
+ x_seg_pos += 1
+
+ # gather up our digits
+ while (x_seg_pos <= x_pos_max) and isdigit(x[x_seg_pos])
+ x_seg_pos += 1
+ end
+ # copy the segment but not the unmatched character that x_seg_pos will
+ # refer to
+ x_comp = x[x_pos,x_seg_pos - x_pos]
+
+ while (y_seg_pos <= y_pos_max) and isdigit(y[y_seg_pos])
+ y_seg_pos += 1
+ end
+ y_comp = y[y_pos,y_seg_pos - y_pos]
+ else
+ # we are comparing strings
+ x_seg_is_num = false
+
+ while (x_seg_pos <= x_pos_max) and isalpha(x[x_seg_pos])
+ x_seg_pos += 1
+ end
+ x_comp = x[x_pos,x_seg_pos - x_pos]
+
+ while (y_seg_pos <= y_pos_max) and isalpha(y[y_seg_pos])
+ y_seg_pos += 1
+ end
+ y_comp = y[y_pos,y_seg_pos - y_pos]
+ end
+
+ # if y_seg_pos didn't advance in the above loop it means the segments are
+ # different types
+ if y_pos == y_seg_pos
+ # numbers always win over letters
+ return x_seg_is_num ? 1 : -1
+ end
+
+ # move the ball forward before we mess with the segments
+ x_pos += x_comp.length # +1 over pos_max if end of string
+ y_pos += y_comp.length
+
+ # we are comparing numbers - simply convert them
+ if x_seg_is_num
+ x_comp = x_comp.to_i
+ y_comp = y_comp.to_i
+ end
+
+ # compares ints or strings
+ # don't return if equal - try the next segment
+ if x_comp > y_comp
+ return 1
+ elsif x_comp < y_comp
+ return -1
+ end
+
+ # if we've reached here than the segments are the same - try again
+ end
+
+ # we must have reached the end of one or both of the strings and they
+ # matched up until this point
+
+ # segments matched completely but the segment separators were different -
+ # rpm reference code treats these as equal.
+ if (x_pos == x_pos_max + 1) and (y_pos == y_pos_max + 1)
+ return 0
+ end
+
+ # the most unprocessed characters left wins
+ if (x_pos_max - x_pos) > (y_pos_max - y_pos)
+ return 1
+ else
+ return -1
+ end
+ end
+
+ end # self
+ end # RPMUtils
+
+ class RPMVersion
+ include Comparable
+
+ def initialize(*args)
+ if args.size == 1
+ @e, @v, @r = RPMUtils.version_parse(args[0])
+ elsif args.size == 3
+ @e = args[0].to_i
+ @v = args[1]
+ @r = args[2]
+ else
+ raise ArgumentError, "Expecting either 'epoch-version-release' or 'epoch, " +
+ "version, release'"
+ end
+ end
+ attr_reader :e, :v, :r
+ alias :epoch :e
+ alias :version :v
+ alias :release :r
+
+ def self.parse(*args)
+ self.new(*args)
+ end
+
+ def <=>(y)
+ compare_versions(y)
+ end
+
+ def compare(y)
+ compare_versions(y, false)
+ end
+
+ def partial_compare(y)
+ compare_versions(y, true)
+ end
+
+ # RPM::Version rpm_version_to_s equivalent
+ def to_s
+ if @r.nil?
+ @v
+ else
+ "#{@v}-#{@r}"
+ end
+ end
+
+ def evr
+ "#{@e}:#{@v}-#{@r}"
+ end
+
+ private
+
+ # Rough RPM::Version rpm_version_cmp equivalent - except much slower :)
+ #
+ # partial lets epoch and version segment equality be good enough to return equal, eg:
+ #
+ # 2:1.2-1 == 2:1.2
+ # 2:1.2-1 == 2:
+ #
+ def compare_versions(y, partial=false)
+ x = self
+
+ # compare epoch
+ if (x.e.nil? == false and x.e > 0) and y.e.nil?
+ return 1
+ elsif x.e.nil? and (y.e.nil? == false and y.e > 0)
+ return -1
+ elsif x.e.nil? == false and y.e.nil? == false
+ if x.e < y.e
+ return -1
+ elsif x.e > y.e
+ return 1
+ end
+ end
+
+ # compare version
+ if partial and (x.v.nil? or y.v.nil?)
+ return 0
+ elsif x.v.nil? == false and y.v.nil?
+ return 1
+ elsif x.v.nil? and y.v.nil? == false
+ return -1
+ elsif x.v.nil? == false and y.v.nil? == false
+ cmp = RPMUtils.rpmvercmp(x.v, y.v)
+ return cmp if cmp != 0
+ end
+
+ # compare release
+ if partial and (x.r.nil? or y.r.nil?)
+ return 0
+ elsif x.r.nil? == false and y.r.nil?
+ return 1
+ elsif x.r.nil? and y.r.nil? == false
+ return -1
+ elsif x.r.nil? == false and y.r.nil? == false
+ cmp = RPMUtils.rpmvercmp(x.r, y.r)
+ return cmp
+ end
+
+ return 0
+ end
+ end
+
+ class RPMPackage
+ include Comparable
+
+ def initialize(*args)
+ if args.size == 4
+ @n = args[0]
+ @version = RPMVersion.new(args[1])
+ @a = args[2]
+ @provides = args[3]
+ elsif args.size == 6
+ @n = args[0]
+ e = args[1].to_i
+ v = args[2]
+ r = args[3]
+ @version = RPMVersion.new(e,v,r)
+ @a = args[4]
+ @provides = args[5]
+ else
+ raise ArgumentError, "Expecting either 'name, epoch-version-release, arch, provides' " +
+ "or 'name, epoch, version, release, arch, provides'"
+ end
+
+ # We always have one, ourselves!
+ if @provides.empty?
+ @provides = [ RPMProvide.new(@n, @version.evr, :==) ]
+ end
+ end
+ attr_reader :n, :a, :version, :provides
+ alias :name :n
+ alias :arch :a
+
+ def <=>(y)
+ compare(y)
+ end
+
+ def compare(y)
+ x = self
+
+ # easy! :)
+ return 0 if x.nevra == y.nevra
+
+ # compare name
+ if x.n.nil? == false and y.n.nil?
+ return 1
+ elsif x.n.nil? and y.n.nil? == false
+ return -1
+ elsif x.n.nil? == false and y.n.nil? == false
+ if x.n < y.n
+ return -1
+ elsif x.n > y.n
+ return 1
+ end
+ end
+
+ # compare version
+ if x.version > y.version
+ return 1
+ elsif x.version < y.version
+ return -1
+ end
+
+ # compare arch
+ if x.a.nil? == false and y.a.nil?
+ return 1
+ elsif x.a.nil? and y.a.nil? == false
+ return -1
+ elsif x.a.nil? == false and y.a.nil? == false
+ if x.a < y.a
+ return -1
+ elsif x.a > y.a
+ return 1
+ end
+ end
+
+ return 0
+ end
+
+ def to_s
+ nevra
+ end
+
+ def nevra
+ "#{@n}-#{@version.evr}.#{@a}"
+ end
+ end
+
+ # Simple implementation from rpm and ruby-rpm reference code
+ class RPMDependency
+ def initialize(*args)
+ if args.size == 3
+ @name = args[0]
+ @version = RPMVersion.new(args[1])
+ # Our requirement to other dependencies
+ @flag = args[2] || :==
+ elsif args.size == 5
+ @name = args[0]
+ e = args[1].to_i
+ v = args[2]
+ r = args[3]
+ @version = RPMVersion.new(e,v,r)
+ @flag = args[4] || :==
+ else
+ raise ArgumentError, "Expecting either 'name, epoch-version-release, flag' or " +
+ "'name, epoch, version, release, flag'"
+ end
+ end
+ attr_reader :name, :version, :flag
+
+ # Parses 2 forms:
+ #
+ # "mtr >= 2:0.71-3.0"
+ # "mta"
+ def self.parse(string)
+ if string =~ %r{^(\S+)\s+(>|>=|=|==|<=|<)\s+(\S+)$}
+ name = $1
+ if $2 == "="
+ flag = :==
+ else
+ flag = :"#{$2}"
+ end
+ version = $3
+
+ return self.new(name, version, flag)
+ else
+ name = string
+ return self.new(name, nil, nil)
+ end
+ end
+
+ # Test if another RPMDependency satisfies our requirements
+ def satisfy?(y)
+ unless y.kind_of?(RPMDependency)
+ raise ArgumentError, "Expecting an RPMDependency object"
+ end
+
+ x = self
+
+ # Easy!
+ if x.name != y.name
+ return false
+ end
+
+ # Partial compare
+ #
+ # eg: x.version 2.3 == y.version 2.3-1
+ sense = x.version.partial_compare(y.version)
+
+ # Thanks to rpmdsCompare() rpmds.c
+ if sense < 0 and (x.flag == :> || x.flag == :>=) || (y.flag == :<= || y.flag == :<)
+ return true
+ elsif sense > 0 and (x.flag == :< || x.flag == :<=) || (y.flag == :>= || y.flag == :>)
+ return true
+ elsif sense == 0 and (
+ ((x.flag == :== or x.flag == :<= or x.flag == :>=) and (y.flag == :== or y.flag == :<= or y.flag == :>=)) or
+ (x.flag == :< and y.flag == :<) or
+ (x.flag == :> and y.flag == :>)
+ )
+ return true
+ end
+
+ return false
+ end
+ end
+
+ class RPMProvide < RPMDependency; end
+ class RPMRequire < RPMDependency; end
+
+ class RPMDbPackage < RPMPackage
+ # <rpm parts>, installed, available
+ def initialize(*args)
+ @repoid = args.pop
+ # state
+ @available = args.pop
+ @installed = args.pop
+ super(*args)
+ end
+ attr_reader :repoid, :available, :installed
+ end
+
+ # Simple storage for RPMPackage objects - keeps them unique and sorted
+ class RPMDb
+ def initialize
+ # package name => [ RPMPackage, RPMPackage ] of different versions
+ @rpms = Hash.new
+ # package nevra => RPMPackage for lookups
+ @index = Hash.new
+ # provide name (aka feature) => [RPMPackage, RPMPackage] each providing this feature
+ @provides = Hash.new
+ # RPMPackages listed as available
+ @available = Set.new
+ # RPMPackages listed as installed
+ @installed = Set.new
+ end
+
+ def [](package_name)
+ self.lookup(package_name)
+ end
+
+ # Lookup package_name and return a descending array of package objects
+ def lookup(package_name)
+ pkgs = @rpms[package_name]
+ if pkgs
+ return pkgs.sort.reverse
+ else
+ return nil
+ end
+ end
+
+ def lookup_provides(provide_name)
+ @provides[provide_name]
+ end
+
+ # Using the package name as a key, and nevra for an index, keep a unique list of packages.
+ # The available/installed state can be overwritten for existing packages.
+ def push(*args)
+ args.flatten.each do |new_rpm|
+ unless new_rpm.kind_of?(RPMDbPackage)
+ raise ArgumentError, "Expecting an RPMDbPackage object"
+ end
+
+ @rpms[new_rpm.n] ||= Array.new
+
+ # we may already have this one, like when the installed list is refreshed
+ idx = @index[new_rpm.nevra]
+ if idx
+ # grab the existing package if it's not
+ curr_rpm = idx
+ else
+ @rpms[new_rpm.n] << new_rpm
+
+ new_rpm.provides.each do |provide|
+ @provides[provide.name] ||= Array.new
+ @provides[provide.name] << new_rpm
+ end
+
+ curr_rpm = new_rpm
+ end
+
+ # Track the nevra -> RPMPackage association to avoid having to compare versions
+ # with @rpms[new_rpm.n] on the next round
+ @index[new_rpm.nevra] = curr_rpm
+
+ # these are overwritten for existing packages
+ if new_rpm.available
+ @available << curr_rpm
+ end
+ if new_rpm.installed
+ @installed << curr_rpm
+ end
+ end
+ end
+
+ def <<(*args)
+ self.push(args)
+ end
+
+ def clear
+ @rpms.clear
+ @index.clear
+ @provides.clear
+ clear_available
+ clear_installed
+ end
+
+ def clear_available
+ @available.clear
+ end
+
+ def clear_installed
+ @installed.clear
+ end
+
+ def size
+ @rpms.size
+ end
+ alias :length :size
+
+ def available_size
+ @available.size
+ end
+
+ def installed_size
+ @installed.size
+ end
+
+ def available?(package)
+ @available.include?(package)
+ end
+
+ def installed?(package)
+ @installed.include?(package)
+ end
+
+ def whatprovides(rpmdep)
+ unless rpmdep.kind_of?(RPMDependency)
+ raise ArgumentError, "Expecting an RPMDependency object"
+ end
+
+ what = []
+
+ packages = lookup_provides(rpmdep.name)
+ if packages
+ packages.each do |pkg|
+ pkg.provides.each do |provide|
+ if provide.satisfy?(rpmdep)
+ what << pkg
+ end
+ end
+ end
+ end
+
+ return what
+ end
+ end
+
+ # Cache for our installed and available packages, pulled in from yum-dump.py
+ class YumCache
+ include Chef::Mixin::Command
+ include Singleton
+
+ def initialize
+ @rpmdb = RPMDb.new
+
+ # Next time @rpmdb is accessed:
+ # :all - Trigger a run of "yum-dump.py --options --installed-provides", updates
+ # yum's cache and parses options from /etc/yum.conf. Pulls in Provides
+ # dependency data for installed packages only - this data is slow to
+ # gather.
+ # :provides - Same as :all but pulls in Provides data for available packages as well.
+ # Used as a last resort when we can't find a Provides match.
+ # :installed - Trigger a run of "yum-dump.py --installed", only reads the local rpm
+ # db. Used between client runs for a quick refresh.
+ # :none - Do nothing, a call to one of the reload methods is required.
+ @next_refresh = :all
+
+ @allow_multi_install = []
+
+ # these are for subsequent runs if we are on an interval
+ Chef::Client.when_run_starts do
+ YumCache.instance.reload
+ end
+ end
+
+ # Cache management
+ #
+
+ def refresh
+ case @next_refresh
+ when :none
+ return nil
+ when :installed
+ reset_installed
+ # fast
+ opts=" --installed"
+ when :all
+ reset
+ # medium
+ opts=" --options --installed-provides"
+ when :provides
+ reset
+ # slow!
+ opts=" --options --all-provides"
+ else
+ raise ArgumentError, "Unexpected value in next_refresh: #{@next_refresh}"
+ end
+
+ one_line = false
+ error = nil
+
+ helper = ::File.join(::File.dirname(__FILE__), 'yum-dump.py')
+
+ status = popen4("/usr/bin/python #{helper}#{opts}", :waitlast => true) do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ one_line = true
+
+ line.chomp!
+
+ if line =~ %r{\[option (.*)\] (.*)}
+ if $1 == "installonlypkgs"
+ @allow_multi_install = $2.split
+ else
+ raise Chef::Exceptions::Package, "Strange, unknown option line '#{line}' from yum-dump.py"
+ end
+ next
+ end
+
+ if line =~ %r{^(\S+) ([0-9]+) (\S+) (\S+) (\S+) \[(.*)\] ([i,a,r]) (\S+)$}
+ name = $1
+ epoch = $2
+ version = $3
+ release = $4
+ arch = $5
+ provides = parse_provides($6)
+ type = $7
+ repoid = $8
+ else
+ Chef::Log.warn("Problem parsing line '#{line}' from yum-dump.py! " +
+ "Please check your yum configuration.")
+ next
+ end
+
+ case type
+ when "i"
+ # if yum-dump was called with --installed this may not be true, but it's okay
+ # since we don't touch the @available Set in reload_installed
+ available = false
+ installed = true
+ when "a"
+ available = true
+ installed = false
+ when "r"
+ available = true
+ installed = true
+ end
+
+ pkg = RPMDbPackage.new(name, epoch, version, release, arch, provides, installed, available, repoid)
+ @rpmdb << pkg
+ end
+
+ error = stderr.readlines
+ end
+
+ if status.exitstatus != 0
+ raise Chef::Exceptions::Package, "Yum failed - #{status.inspect} - returns: #{error}"
+ else
+ unless one_line
+ Chef::Log.warn("Odd, no output from yum-dump.py. Please check " +
+ "your yum configuration.")
+ end
+ end
+
+ # A reload method must be called before the cache is altered
+ @next_refresh = :none
+ end
+
+ def reload
+ @next_refresh = :all
+ end
+
+ def reload_installed
+ @next_refresh = :installed
+ end
+
+ def reload_provides
+ @next_refresh = :provides
+ end
+
+ def reset
+ @rpmdb.clear
+ end
+
+ def reset_installed
+ @rpmdb.clear_installed
+ end
+
+ # Querying the cache
+ #
+
+ # Check for package by name or name+arch
+ def package_available?(package_name)
+ refresh
+
+ if @rpmdb.lookup(package_name)
+ return true
+ else
+ if package_name =~ %r{^(.*)\.(.*)$}
+ pkg_name = $1
+ pkg_arch = $2
+
+ if matches = @rpmdb.lookup(pkg_name)
+ matches.each do |m|
+ return true if m.arch == pkg_arch
+ end
+ end
+ end
+ end
+
+ return false
+ end
+
+ # Returns a array of packages satisfying an RPMDependency
+ def packages_from_require(rpmdep)
+ refresh
+ @rpmdb.whatprovides(rpmdep)
+ end
+
+ # Check if a package-version.arch is available to install
+ def version_available?(package_name, desired_version, arch=nil)
+ version(package_name, arch, true, false) do |v|
+ return true if desired_version == v
+ end
+
+ return false
+ end
+
+ # Return the source repository for a package-version.arch
+ def package_repository(package_name, desired_version, arch=nil)
+ package(package_name, arch, true, false) do |pkg|
+ return pkg.repoid if desired_version == pkg.version.to_s
+ end
+
+ return nil
+ end
+
+ # Return the latest available version for a package.arch
+ def available_version(package_name, arch=nil)
+ version(package_name, arch, true, false)
+ end
+ alias :candidate_version :available_version
+
+ # Return the currently installed version for a package.arch
+ def installed_version(package_name, arch=nil)
+ version(package_name, arch, false, true)
+ end
+
+ # Return an array of packages allowed to be installed multiple times, such as the kernel
+ def allow_multi_install
+ refresh
+ @allow_multi_install
+ end
+
+ private
+
+ def version(package_name, arch=nil, is_available=false, is_installed=false)
+ package(package_name, arch, is_available, is_installed) do |pkg|
+ if block_given?
+ yield pkg.version.to_s
+ else
+ # first match is latest version
+ return pkg.version.to_s
+ end
+ end
+
+ if block_given?
+ return self
+ else
+ return nil
+ end
+ end
+
+ def package(package_name, arch=nil, is_available=false, is_installed=false)
+ refresh
+ packages = @rpmdb[package_name]
+ if packages
+ packages.each do |pkg|
+ if is_available
+ next unless @rpmdb.available?(pkg)
+ end
+ if is_installed
+ next unless @rpmdb.installed?(pkg)
+ end
+ if arch
+ next unless pkg.arch == arch
+ end
+
+ if block_given?
+ yield pkg
+ else
+ # first match is latest version
+ return pkg
+ end
+ end
+ end
+
+ if block_given?
+ return self
+ else
+ return nil
+ end
+ end
+
+ # Parse provides from yum-dump.py output
+ def parse_provides(string)
+ ret = []
+ # ['atk = 1.12.2-1.fc6', 'libatk-1.0.so.0']
+ string.split(", ").each do |seg|
+ # 'atk = 1.12.2-1.fc6'
+ if seg =~ %r{^'(.*)'$}
+ ret << RPMProvide.parse($1)
+ end
+ end
+
+ return ret
+ end
+
+ end # YumCache
+
+ include Chef::Mixin::GetSourceFromPackage
+
+ def initialize(new_resource, run_context)
+ super
+
+ @yum = YumCache.instance
+ end
+
+ # Extra attributes
+ #
+
+ def arch
+ if @new_resource.respond_to?("arch")
+ @new_resource.arch
+ else
+ nil
+ end
+ end
+
+ def flush_cache
+ if @new_resource.respond_to?("flush_cache")
+ @new_resource.flush_cache
+ else
+ { :before => false, :after => false }
+ end
+ end
+
+ def allow_downgrade
+ if @new_resource.respond_to?("allow_downgrade")
+ @new_resource.allow_downgrade
+ else
+ false
+ end
+ end
+
+ # Helpers
+ #
+
+ def yum_arch
+ arch ? ".#{arch}" : nil
+ end
+
+ def yum_command(command)
+ status, stdout, stderr = output_of_command(command, {})
+
+ # This is fun: rpm can encounter errors in the %post/%postun scripts which aren't
+ # considered fatal - meaning the rpm is still successfully installed. These issue
+ # cause yum to emit a non fatal warning but still exit(1). As there's currently no
+ # way to suppress this behavior and an exit(1) will break a Chef run we make an
+ # effort to trap these and re-run the same install command - it will either fail a
+ # second time or succeed.
+ #
+ # A cleaner solution would have to be done in python and better hook into
+ # yum/rpm to handle exceptions as we see fit.
+ if status.exitstatus == 1
+ stdout.each_line do |l|
+ # rpm-4.4.2.3 lib/psm.c line 2182
+ if l =~ %r{^error: %(post|postun)\(.*\) scriptlet failed, exit status \d+$}
+ Chef::Log.warn("#{@new_resource} caught non-fatal scriptlet issue: \"#{l}\". Can't trust yum exit status " +
+ "so running install again to verify.")
+ status, stdout, stderr = output_of_command(command, {})
+ break
+ end
+ end
+ end
+
+ if status.exitstatus > 0
+ command_output = "STDOUT: #{stdout}"
+ command_output << "STDERR: #{stderr}"
+ handle_command_failures(status, command_output, {})
+ end
+ end
+
+ # Standard Provider methods for Parent
+ #
+
+ def load_current_resource
+ if flush_cache[:before]
+ @yum.reload
+ end
+
+ # At this point package_name could be:
+ #
+ # 1) a package name, eg: "foo"
+ # 2) a package name.arch, eg: "foo.i386"
+ # 3) or a dependency, eg: "foo >= 1.1"
+
+ # Check if we have name or name+arch which has a priority over a dependency
+ unless @yum.package_available?(@new_resource.package_name)
+ # If they aren't in the installed packages they could be a dependency
+ parse_dependency
+ end
+
+ # Don't overwrite an existing arch
+ unless arch
+ parse_arch
+ end
+
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+
+ if @new_resource.source
+ unless ::File.exists?(@new_resource.source)
+ raise Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
+ end
+
+ Chef::Log.debug("#{@new_resource} checking rpm status")
+ status = popen4("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' #{@new_resource.source}") do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ case line
+ when /([\w\d_.-]+)\s([\w\d_.-]+)/
+ @current_resource.package_name($1)
+ @new_resource.version($2)
+ end
+ end
+ end
+ end
+
+ if @new_resource.version
+ new_resource = "#{@new_resource.package_name}-#{@new_resource.version}#{yum_arch}"
+ else
+ new_resource = "#{@new_resource.package_name}#{yum_arch}"
+ end
+
+ Chef::Log.debug("#{@new_resource} checking yum info for #{new_resource}")
+
+ installed_version = @yum.installed_version(@new_resource.package_name, arch)
+ @current_resource.version(installed_version)
+
+ @candidate_version = @yum.candidate_version(@new_resource.package_name, arch)
+
+ Chef::Log.debug("#{@new_resource} installed version: #{installed_version || "(none)"} candidate version: " +
+ "#{@candidate_version || "(none)"}")
+
+ @current_resource
+ end
+
+ def install_package(name, version)
+ if @new_resource.source
+ yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} localinstall #{@new_resource.source}")
+ else
+ # Work around yum not exiting with an error if a package doesn't exist for CHEF-2062
+ if @yum.version_available?(name, version, arch)
+ method = "install"
+ log_method = "installing"
+
+ # More Yum fun:
+ #
+ # yum install of an old name+version will exit(1)
+ # yum install of an old name+version+arch will exit(0) for some reason
+ #
+ # Some packages can be installed multiple times like the kernel
+ unless @yum.allow_multi_install.include?(name)
+ if RPMVersion.parse(@current_resource.version) > RPMVersion.parse(version)
+ # Unless they want this...
+ if allow_downgrade
+ method = "downgrade"
+ log_method = "downgrading"
+ else
+ # we bail like yum when the package is older
+ raise Chef::Exceptions::Package, "Installed package #{name}-#{@current_resource.version} is newer " +
+ "than candidate package #{name}-#{version}"
+ end
+ end
+ end
+
+ repo = @yum.package_repository(name, version, arch)
+ Chef::Log.info("#{@new_resource} #{log_method} #{name}-#{version}#{yum_arch} from #{repo} repository")
+
+ yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} #{method} #{name}-#{version}#{yum_arch}")
+ else
+ raise Chef::Exceptions::Package, "Version #{version} of #{name} not found. Did you specify both version " +
+ "and release? (version-release, e.g. 1.84-10.fc6)"
+ end
+ end
+
+ if flush_cache[:after]
+ @yum.reload
+ else
+ @yum.reload_installed
+ end
+ end
+
+ # Keep upgrades from trying to install an older candidate version. Can happen when a new
+ # version is installed then removed from a repository, now the older available version
+ # shows up as a viable install candidate.
+ #
+ # Can be done in upgrade_package but an upgraded from->to log message slips out
+ #
+ # Hacky - better overall solution? Custom compare in Package provider?
+ def action_upgrade
+ # Could be uninstalled or have no candidate
+ if @current_resource.version.nil? || candidate_version.nil?
+ super
+ # Ensure the candidate is newer
+ elsif RPMVersion.parse(candidate_version) > RPMVersion.parse(@current_resource.version)
+ super
+ else
+ Chef::Log.debug("#{@new_resource} is at the latest version - nothing to do")
+ end
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ if version
+ yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} remove #{name}-#{version}#{yum_arch}")
+ else
+ yum_command("yum -d0 -e0 -y#{expand_options(@new_resource.options)} remove #{name}#{yum_arch}")
+ end
+
+ if flush_cache[:after]
+ @yum.reload
+ else
+ @yum.reload_installed
+ end
+ end
+
+ def purge_package(name, version)
+ remove_package(name, version)
+ end
+
+ private
+
+ def parse_arch
+ # Allow for foo.x86_64 style package_name like yum uses in it's output
+ #
+ if @new_resource.package_name =~ %r{^(.*)\.(.*)$}
+ new_package_name = $1
+ new_arch = $2
+ # foo.i386 and foo.beta1 are both valid package names or expressions of an arch.
+ # Ensure we don't have an existing package matching package_name, then ensure we at
+ # least have a match for the new_package+new_arch before we overwrite. If neither
+ # then fall through to standard package handling.
+ if (@yum.installed_version(@new_resource.package_name).nil? and @yum.candidate_version(@new_resource.package_name).nil?) and
+ (@yum.installed_version(new_package_name, new_arch) or @yum.candidate_version(new_package_name, new_arch))
+ @new_resource.package_name(new_package_name)
+ @new_resource.arch(new_arch)
+ end
+ end
+ end
+
+ # If we don't have the package we could have been passed a 'whatprovides' feature
+ #
+ # eg: yum install "perl(Config)"
+ # yum install "mtr = 2:0.71-3.1"
+ # yum install "mtr > 2:0.71"
+ #
+ # We support resolving these out of the Provides data imported from yum-dump.py and
+ # matching them up with an actual package so the standard resource handling can apply.
+ #
+ # There is currently no support for filename matching.
+ def parse_dependency
+ # Transform the package_name into a requirement
+ yum_require = RPMRequire.parse(@new_resource.package_name)
+ # and gather all the packages that have a Provides feature satisfying the requirement.
+ # It could be multiple be we can only manage one
+ packages = @yum.packages_from_require(yum_require)
+
+ if packages.empty?
+ # Don't bother if we are just ensuring a package is removed - we don't need Provides data
+ actions = Array(@new_resource.action)
+ unless actions.size == 1 and (actions[0] == :remove || actions[0] == :purge)
+ Chef::Log.debug("#{@new_resource} couldn't match #{@new_resource.package_name} in " +
+ "installed Provides, loading available Provides - this may take a moment")
+ @yum.reload_provides
+ packages = @yum.packages_from_require(yum_require)
+ end
+ end
+
+ unless packages.empty?
+ new_package_name = packages.first.name
+ Chef::Log.debug("#{@new_resource} no package found for #{@new_resource.package_name} " +
+ "but matched Provides for #{new_package_name}")
+
+ # Ensure it's not the same package under a different architecture
+ unique_names = []
+ packages.each do |pkg|
+ unique_names << "#{pkg.name}-#{pkg.version.evr}"
+ end
+ unique_names.uniq!
+
+ if unique_names.size > 1
+ Chef::Log.warn("#{@new_resource} matched multiple Provides for #{@new_resource.package_name} " +
+ "but we can only use the first match: #{new_package_name}. Please use a more " +
+ "specific version.")
+ end
+
+ @new_resource.package_name(new_package_name)
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/provider/package/zypper.rb b/lib/chef/provider/package/zypper.rb
new file mode 100644
index 0000000000..43727466e2
--- /dev/null
+++ b/lib/chef/provider/package/zypper.rb
@@ -0,0 +1,144 @@
+#
+# Author:: Adam Jacob (<adam@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, 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 'chef/provider/package'
+require 'chef/mixin/command'
+require 'chef/resource/package'
+require 'singleton'
+
+class Chef
+ class Provider
+ class Package
+ class Zypper < Chef::Provider::Package
+
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+
+ is_installed=false
+ is_out_of_date=false
+ version=''
+ oud_version=''
+ Chef::Log.debug("#{@new_resource} checking zypper")
+ status = popen4("zypper info #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdout.each do |line|
+ case line
+ when /^Version: (.+)$/
+ version = $1
+ Chef::Log.debug("#{@new_resource} version #{$1}")
+ when /^Installed: Yes$/
+ is_installed=true
+ Chef::Log.debug("#{@new_resource} is installed")
+
+ when /^Installed: No$/
+ is_installed=false
+ Chef::Log.debug("#{@new_resource} is not installed")
+ when /^Status: out-of-date \(version (.+) installed\)$/
+ is_out_of_date=true
+ oud_version=$1
+ Chef::Log.debug("#{@new_resource} out of date version #{$1}")
+ end
+ end
+ end
+
+ if is_installed==false
+ @candidate_version=version
+ @current_resource.version(nil)
+ end
+
+ if is_installed==true
+ if is_out_of_date==true
+ @current_resource.version(oud_version)
+ @candidate_version=version
+ else
+ @current_resource.version(version)
+ @candidate_version=version
+ end
+ end
+
+ unless status.exitstatus == 0
+ raise Chef::Exceptions::Package, "zypper failed - #{status.inspect}!"
+ end
+
+ @current_resource
+ end
+
+ #Gets the zypper Version from command output (Returns Floating Point number)
+ def zypper_version()
+ `zypper -V 2>&1`.scan(/\d+/).join(".").to_f
+ end
+
+ def install_package(name, version)
+ if zypper_version < 1.0
+ run_command(
+ :command => "zypper install -y #{name}"
+ )
+ elsif version
+ run_command(
+ :command => "zypper -n --no-gpg-checks install -l #{name}=#{version}"
+ )
+ else
+ run_command(
+ :command => "zypper -n --no-gpg-checks install -l #{name}"
+ )
+ end
+ end
+
+ def upgrade_package(name, version)
+ if zypper_version < 1.0
+ run_command(
+ :command => "zypper install -y #{name}"
+ )
+ elsif version
+ run_command(
+ :command => "zypper -n --no-gpg-checks install -l #{name}=#{version}"
+ )
+ else
+ run_command(
+ :command => "zypper -n --no-gpg-checks install -l #{name}"
+ )
+ end
+ end
+
+ def remove_package(name, version)
+ if zypper_version < 1.0
+ run_command(
+ :command => "zypper remove -y #{name}"
+ )
+ elsif version
+ run_command(
+ :command => "zypper -n --no-gpg-checks remove #{name}=#{version}"
+ )
+ else
+ run_command(
+ :command => "zypper -n --no-gpg-checks remove #{name}"
+ )
+ end
+
+
+ end
+
+ def purge_package(name, version)
+ remove_package(name, version)
+ end
+
+ end
+ end
+ end
+end