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] + } + +}