Created
February 5, 2014 00:40
-
-
Save gmr/8815345 to your computer and use it in GitHub Desktop.
Replacement python_pip Chef provider that calls out to package index servers to get the latest version for candidate_version, reducing duplicate downloads and unneeded restarts.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# Author:: Seth Chisamore <schisamo@opscode.com> | |
# Cookbook Name:: python | |
# Provider:: pip | |
# | |
# Copyright:: 2011, Opscode, Inc <legal@opscode.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. | |
# | |
require 'uri' | |
require 'zlib' | |
require 'chef/mixin/shell_out' | |
require 'chef/mixin/language' | |
require 'chef/rest' | |
include Chef::Mixin::ShellOut | |
def whyrun_supported? | |
true | |
end | |
# the logic in all action methods mirror that of | |
# the Chef::Provider::Package which will make | |
# refactoring into core chef easy | |
action :install do | |
# If we specified a version, and it's not the current version, move to the specified version | |
if new_resource.version != nil && new_resource.version != current_resource.version | |
install_version = new_resource.version | |
# If it's not installed at all, install it | |
elsif current_resource.version == nil | |
install_version = candidate_version | |
end | |
if install_version | |
description = "install package #{new_resource} version #{install_version}" | |
converge_by(description) do | |
Chef::Log.info("Installing #{new_resource} version #{install_version}") | |
status = install_package(install_version) | |
if status | |
new_resource.updated_by_last_action(true) | |
end | |
end | |
end | |
end | |
action :upgrade do | |
if current_resource.version != candidate_version | |
orig_version = current_resource.version || "uninstalled" | |
description = "upgrade #{current_resource} version from #{current_resource.version} to #{candidate_version}" | |
converge_by(description) do | |
Chef::Log.info("Upgrading #{new_resource} version from #{orig_version} to #{candidate_version}") | |
status = upgrade_package(candidate_version) | |
if status | |
new_resource.updated_by_last_action(true) | |
end | |
end | |
end | |
end | |
action :remove do | |
if removing_package? | |
description = "remove package #{new_resource}" | |
converge_by(description) do | |
Chef::Log.info("Removing #{new_resource}") | |
remove_package(new_resource.version) | |
new_resource.updated_by_last_action(true) | |
end | |
end | |
end | |
def removing_package? | |
if current_resource.version.nil? | |
false # nothing to remove | |
elsif new_resource.version.nil? | |
true # remove any version of a package | |
elsif new_resource.version == current_resource.version | |
true # remove the version we have | |
else | |
false # we don't have the version we want to remove | |
end | |
end | |
# these methods are the required overrides of | |
# a provider that extends from Chef::Provider::Package | |
# so refactoring into core Chef should be easy | |
def load_current_resource | |
@current_resource = Chef::Resource::PythonPip.new(new_resource.name) | |
@current_resource.package_name(new_resource.package_name) | |
@current_resource.version(nil) | |
unless current_installed_version.nil? | |
@current_resource.version(current_installed_version) | |
end | |
@current_resource | |
end | |
def current_installed_version | |
@current_installed_version ||= begin | |
delimeter = /==/ | |
version_check_cmd = "#{which_pip(new_resource)} freeze | grep -i '^#{new_resource.package_name}=='" | |
# incase you upgrade pip with pip! | |
if new_resource.package_name.eql?('pip') | |
delimeter = /\s/ | |
version_check_cmd = "#{which_pip(@new_resource)} --version" | |
end | |
result = shell_out(version_check_cmd) | |
(result.exitstatus == 0) ? result.stdout.split(delimeter)[1].strip : nil | |
end | |
end | |
def candidate_version | |
version = 'latest' | |
[node[:python][:index_url], node[:python][:extra_index_url]].each do |site| | |
url = URI.parse("#{site}/#{new_resource.package_name}/") | |
response = Chef::REST::RESTRequest.new('GET', url, '').call | |
if response.kind_of?(Net::HTTPSuccess) | |
body = decompress_body(response) | |
matches = body.scan(/<a href=".*">\w+-(?<version>[\d.]+).tar.gz<\/a>/) | |
versions = matches.flatten.sort | |
version = versions.pop || 'latest' | |
break | |
end | |
end | |
Chef::Log.info("Candidate version: #{version}") | |
version | |
end | |
def install_package(version) | |
# if a version isn't specified (latest), is a source archive (ex. http://my.package.repo/SomePackage-1.0.4.zip), | |
# or from a VCS (ex. git+https://git.repo/some_pkg.git) then do not append a version as this will break the source link | |
if version == 'latest' || new_resource.package_name.downcase.start_with?('http:', 'https:') || ['git', 'hg', 'svn'].include?(new_resource.package_name.downcase.split('+')[0]) | |
version = '' | |
else | |
version = "==#{version}" | |
end | |
pip_cmd('install', version) | |
end | |
def upgrade_package(version) | |
new_resource.options "#{new_resource.options} --upgrade" | |
install_package(version) | |
end | |
def remove_package(version) | |
new_resource.options "#{new_resource.options} --yes" | |
pip_cmd('uninstall') | |
end | |
def pip_cmd(subcommand, version='') | |
options = { :timeout => new_resource.timeout, :user => new_resource.user, :group => new_resource.group } | |
environment = Hash.new | |
environment['HOME'] = Dir.home(new_resource.user) if new_resource.user | |
environment.merge!(new_resource.environment) if new_resource.environment && !new_resource.environment.empty? | |
options[:environment] = environment | |
shell_out!("#{which_pip(new_resource)} #{subcommand} #{new_resource.options} #{new_resource.package_name}#{version}", options) | |
end | |
# TODO remove when provider is moved into Chef core | |
# this allows PythonPip to work with Chef::Resource::Package | |
def which_pip(nr) | |
#if (nr.respond_to?("virtualenv") && nr.virtualenv) | |
# ::File.join(nr.virtualenv,'/bin/pip') | |
#elsif ::File.exists?(node['python']['pip_location']) | |
# node['python']['pip_location'] | |
#else | |
'pip' | |
#end | |
end | |
def decompress_body(response) | |
if response.body.nil? | |
response.body | |
else | |
case response['content-encoding'] | |
when 'gzip' | |
Chef::Log.debug "decompressing gzip response" | |
Zlib::Inflate.new(Zlib::MAX_WBITS + 16).inflate(response.body) | |
when 'deflate' | |
Chef::Log.debug "decompressing deflate response" | |
Zlib::Inflate.inflate(response.body) | |
else | |
response.body | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment