diff --git a/lib/puppet/provider/package/pip3.rb b/lib/puppet/provider/package/pip3.rb
new file mode 100644
index 0000000..5afb615
--- /dev/null
+++ b/lib/puppet/provider/package/pip3.rb
@@ -0,0 +1,134 @@
+# Puppet - Automating Configuration Management.
+
+# Copyright (C) 2005-2012 Puppet Labs Inc
+# Copyright 2013 Red Hat, Inc.
+
+# Puppet Labs can be contacted at: info@puppetlabs.com
+
+# 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.
+
+# Puppet package provider for Python's `pip3` package management frontend.
+#
+#
+# Loosely based on the 'pip' package provider in puppet 2.7.
+require 'puppet/provider/package'
+require 'xmlrpc/client'
+
+Puppet::Type.type(:package).provide :pip3,
+ :parent => ::Puppet::Provider::Package do
+
+ desc "Python packages via `python-pip3`."
+
+ has_feature :installable, :uninstallable, :upgradeable, :versionable
+
+ # Parse lines of output from `pip freeze`, which are structured as
+ # _package_==_version_.
+ def self.parse(line)
+ if line.chomp =~ /^([^=]+)==([^=]+)$/
+ {:ensure => $2, :name => $1, :provider => name}
+ else
+ nil
+ end
+ end
+
+ # Return an array of structured information about every installed package
+ # that's managed by `pip` or an empty array if `pip` is not available.
+ def self.instances
+ packages = []
+ execpipe "#{pip3_cmd} freeze" do |process|
+ process.collect do |line|
+ next unless options = parse(line)
+ packages << new(options)
+ end
+ end
+ packages
+ end
+
+ # Return structured information about a particular package or `nil` if
+ # it is not installed or `pip` itself is not available.
+ def query
+ self.class.instances.each do |provider_pip|
+ return provider_pip.properties if @resource[:name] == provider_pip.name
+ end
+ return nil
+ end
+
+ # Ask the PyPI API for the latest version number. There is no local
+ # cache of PyPI's package list so this operation will always have to
+ # ask the web service.
+ def latest
+ client = XMLRPC::Client.new2("http://pypi.python.org/pypi")
+ client.http_header_extra = {"Content-Type" => "text/xml"}
+ client.timeout = 10
+ result = client.call("package_releases", @resource[:name])
+ result.first
+ rescue Timeout::Error => detail
+ raise Puppet::Error, "Timeout while contacting pypi.python.org: #{detail}";
+ end
+
+ # Install a package. The ensure parameter may specify installed,
+ # latest, a version number, or, in conjunction with the source
+ # parameter, an SCM revision. In that case, the source parameter
+ # gives the fully-qualified URL to the repository.
+ def install
+ args = %w{install -q}
+ if @resource[:source]
+ args << "-e"
+ if String === @resource[:ensure]
+ args << "#{@resource[:source]}@#{@resource[:ensure]}#egg=#{
+ @resource[:name]}"
+ else
+ args << "#{@resource[:source]}#egg=#{@resource[:name]}"
+ end
+ else
+ case @resource[:ensure]
+ when String
+ args << "#{@resource[:name]}==#{@resource[:ensure]}"
+ when :latest
+ args << "--upgrade" << @resource[:name]
+ else
+ args << @resource[:name]
+ end
+ end
+ lazy_pip *args
+ end
+
+ # Uninstall a package. Uninstall won't work reliably on Debian/Ubuntu
+ # unless this issue gets fixed.
+ #
+ def uninstall
+ lazy_pip "uninstall", "-y", "-q", @resource[:name]
+ end
+
+ def update
+ install
+ end
+
+ # Execute a `pip` command. If Puppet doesn't yet know how to do so,
+ # try to teach it and if even that fails, raise the error.
+ private
+ def lazy_pip(*args)
+ pip3 *args
+ rescue NoMethodError => e
+ self.class.commands :pip => pip3_cmd
+ pip *args
+ end
+
+ def pip3_cmd
+ ['/usr/bin/python3-pip', '/usr/bin/pip3', '/usr/bin/pip-3.2', '/usr/bin/pip-3.3'].each do |p|
+ return p if File.exist?(p)
+ end
+ raise Puppet::Error, "Unable to fine pip3 binary.";
+ end
+
+end
diff --git a/manifests/params.pp b/manifests/params.pp
index f3b7ae4..856047d 100644
--- a/manifests/params.pp
+++ b/manifests/params.pp
@@ -7,10 +7,14 @@ class pip::params {
'RedHat': {
$python_devel_package = 'python-devel'
$python_pip_package = 'python-pip'
+ $python3_devel_package = 'python3-devel'
+ $python3_pip_package = 'python3-pip'
}
'Debian': {
- $python_devel_package = 'python-all-dev'
- $python_pip_package = 'python-pip'
+ $python_devel_package = 'python-all-dev'
+ $python_pip_package = 'python-pip'
+ $python3_devel_package = 'python3-all-dev'
+ $python3_pip_package = 'python3-pip'
}
default: {
fail("Unsupported osfamily: ${::osfamily} The 'pip' module only supports osfamily Debian or RedHat.")
diff --git a/manifests/python3.pp b/manifests/python3.pp
new file mode 100644
index 0000000..38a2412
--- /dev/null
+++ b/manifests/python3.pp
@@ -0,0 +1,15 @@
+# Class: pip
+#
+class pip::python3 {
+ include pip::params
+
+ package { $::pip::params::python3_devel_package:
+ ensure => present,
+ }
+
+ package { $::pip::params::python3_pip_package:
+ ensure => present,
+ require => Package[$::pip::params::python3_devel_package]
+ }
+
+}