From 96f3452e55c9412e7b8c7da9a69669294b3e47c6 Mon Sep 17 00:00:00 2001 From: Matt Button Date: Tue, 7 Aug 2012 14:53:12 +0100 Subject: [PATCH] Use our own version of pear/pear_channel --- providers/pear.rb | 261 +++++++++++++++++++++++++++++++++++++ providers/pear_channel.rb | 91 +++++++++++++ recipes/PHPUnit.rb | 8 +- recipes/PHP_CodeBrowser.rb | 8 +- recipes/PHP_Depend.rb | 6 +- recipes/PHP_PMD.rb | 6 +- recipes/phpcpd.rb | 8 +- recipes/phploc.rb | 8 +- recipes/vfsStream.rb | 6 +- recipes/zendframework.rb | 6 +- resources/pear.rb | 29 +++++ resources/pear_channel.rb | 28 ++++ 12 files changed, 437 insertions(+), 28 deletions(-) create mode 100644 providers/pear.rb create mode 100644 providers/pear_channel.rb create mode 100644 resources/pear.rb create mode 100644 resources/pear_channel.rb diff --git a/providers/pear.rb b/providers/pear.rb new file mode 100644 index 0000000..4db4556 --- /dev/null +++ b/providers/pear.rb @@ -0,0 +1,261 @@ +# +# Author:: Seth Chisamore +# Cookbook Name:: php +# Provider:: pear_package +# +# Copyright:: 2011, Opscode, Inc +# +# 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/mixin/shell_out' +require 'chef/mixin/language' +include Chef::Mixin::ShellOut + +# 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 + Chef::Log.info("Installing #{@new_resource} version #{install_version}") + status = install_package(@new_resource.package_name, install_version) + if status + @new_resource.updated_by_last_action(true) + end + end +end + +action :upgrade do + if @current_resource.version != candidate_version + orig_version = @current_resource.version || "uninstalled" + Chef::Log.info("Upgrading #{@new_resource} version from #{orig_version} to #{candidate_version}") + status = upgrade_package(@new_resource.package_name, candidate_version) + if status + @new_resource.updated_by_last_action(true) + end + end +end + +action :remove do + if removing_package? + Chef::Log.info("Removing #{@new_resource}") + remove_package(@current_resource.package_name, @new_resource.version) + @new_resource.updated_by_last_action(true) + else + end +end + +action :purge do + if removing_package? + Chef::Log.info("Purging #{@new_resource}") + purge_package(@current_resource.package_name, @new_resource.version) + @new_resource.updated_by_last_action(true) + 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 + +def expand_options(options) + options ? " #{options}" : "" +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::PhpPear.new(@new_resource.name) + @current_resource.package_name(@new_resource.package_name) + @bin = 'pear' + if pecl? + Chef::Log.debug("#{@new_resource} smells like a pecl...installing package in Pecl mode.") + @bin = 'pecl' + end + Chef::Log.debug("#{@current_resource}: Installed version: #{current_installed_version} Candidate version: #{candidate_version}") + + unless current_installed_version.nil? + @current_resource.version(current_installed_version) + Chef::Log.debug("Current version is #{@current_resource.version}") if @current_resource.version + end + @current_resource +end + +def current_installed_version + @current_installed_version ||= begin + v = nil + version_check_cmd = "#{@bin} -d preferred_state=#{can_haz(@new_resource, "preferred_state")} list#{expand_channel(can_haz(@new_resource, "channel"))}" + p = shell_out(version_check_cmd) + response = nil + if p.stdout =~ /\.?Installed packages/i + response = grep_for_version(p.stdout, @new_resource.package_name) + end + response + end +end + +def candidate_version + @candidate_version ||= begin + candidate_version_cmd = "#{@bin} -d preferred_state=#{can_haz(@new_resource, "preferred_state")} search#{expand_channel(can_haz(@new_resource, "channel"))} #{@new_resource.package_name}" + p = shell_out(candidate_version_cmd) + response = nil + if p.stdout =~ /\.?Matched packages/i + response = grep_for_version(p.stdout, @new_resource.package_name) + end + response + end +end + +def install_package(name, version) + pear_shell_out("echo -e \"\\r\" | #{@bin} -d preferred_state=#{can_haz(@new_resource, "preferred_state")} install -a#{expand_options(@new_resource.options)} #{prefix_channel(can_haz(@new_resource, "channel"))}#{name}-#{version}") + manage_pecl_ini(name, :create, can_haz(@new_resource, "directives"), can_haz(@new_resource, "zend_extensions")) if pecl? +end + +def upgrade_package(name, version) + pear_shell_out("echo -e \"\\r\" | #{@bin} -d preferred_state=#{can_haz(@new_resource, "preferred_state")} upgrade -a#{expand_options(@new_resource.options)} #{prefix_channel(can_haz(@new_resource, "channel"))}#{name}-#{version}") + manage_pecl_ini(name, :create, can_haz(@new_resource, "directives"), can_haz(@new_resource, "zend_extensions")) if pecl? +end + +def remove_package(name, version) + command = "#{@bin} uninstall #{expand_options(@new_resource.options)} #{prefix_channel(can_haz(@new_resource, "channel"))}#{name}" + command << "-#{version}" if version and !version.empty? + pear_shell_out(command) + manage_pecl_ini(name, :delete) if pecl? +end + +def pear_shell_out(command) + p = shell_out!(command) + # pear/pecl commands return a 0 on failures...we'll grep for it + if p.stdout.split("\n").last =~ /^ERROR:.+/i + p.invalid! + end + p +end + +def purge_package(name, version) + remove_package(name, version) +end + +def expand_channel(channel) + channel ? " -c #{channel}" : "" +end + +def prefix_channel(channel) + channel ? "#{channel}/" : "" +end + +def get_extension_dir() + @extension_dir ||= begin + p = shell_out("php-config --extension-dir") + p.stdout.strip + end +end + +def get_extension_files(name) + files = [] + + p = shell_out("#{@bin} list-files #{name}") + p.stdout.each_line.grep(/^src\s+.*\.so$/i).each do |line| + files << line.split[1] + end + + files +end + +def manage_pecl_ini(name, action, directives, zend_extensions) + ext_prefix = get_extension_dir() + ext_prefix << ::File::SEPARATOR if ext_prefix[-1].chr != ::File::SEPARATOR + + files = get_extension_files(name) + + extensions = Hash[ files.map { |filepath| + rel_file = filepath.clone + rel_file.slice! ext_prefix if rel_file.start_with? ext_prefix + + zend = zend_extensions.include?(rel_file) + + [ (zend ? filepath : rel_file) , zend ] + }] + + template "#{node['php']['ext_conf_dir']}/#{name}.ini" do + source "extension.ini.erb" + cookbook "php" + owner "root" + group "root" + mode "0644" + variables(:name => name, :extensions => extensions, :directives => directives) + action action + end +end + +def grep_for_version(stdout, package) + v = nil + + stdout.split(/\n/).grep(/^#{package}\s/i).each do |m| + # XML_RPC 1.5.4 stable + # mongo 1.1.4/(1.1.4 stable) 1.1.4 MongoDB database driver + # Horde_Url -n/a-/(1.0.0beta1 beta) Horde Url class + # Horde_Url 1.0.0beta1 (beta) 1.0.0beta1 Horde Url class + v = m.split(/\s+/)[1].strip + if v.split(/\//)[0] =~ /.\./ + # 1.1.4/(1.1.4 stable) + v = v.split(/\//)[0] + else + # -n/a-/(1.0.0beta1 beta) + v = v.split(/(.*)\/\((.*)/).last.split(/\s/)[0] + end + end + v +end + +def pecl? + @pecl ||= begin + # search as a pear first since most 3rd party channels will report pears as pecls! + search_cmd = "pear -d preferred_state=#{can_haz(@new_resource, "preferred_state")} search#{expand_channel(can_haz(@new_resource, "channel"))} #{@new_resource.package_name}" + if shell_out(search_cmd).stdout =~ /\.?Matched packages/i + false + else + # fall back and search as a pecl + search_cmd = "pecl -d preferred_state=#{can_haz(@new_resource, "preferred_state")} search#{expand_channel(can_haz(@new_resource, "channel"))} #{@new_resource.package_name}" + if shell_out(search_cmd).stdout =~ /\.?Matched packages/i + true + else + false + end + end + end +end + +# TODO remove when provider is moved into Chef core +# this allows PhpPear to work with Chef::Resource::Package +def can_haz(resource, attribute_name) + resource.respond_to?(attribute_name) ? resource.send(attribute_name) : nil +end diff --git a/providers/pear_channel.rb b/providers/pear_channel.rb new file mode 100644 index 0000000..5355b5a --- /dev/null +++ b/providers/pear_channel.rb @@ -0,0 +1,91 @@ +# +# Author:: Seth Chisamore +# Cookbook Name:: php +# Provider:: pear_channel +# +# Copyright:: 2011, Opscode, Inc +# +# 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. +# + +# http://pear.php.net/manual/en/guide.users.commandline.channels.php + +require 'chef/mixin/shell_out' +require 'chef/mixin/language' +include Chef::Mixin::ShellOut + +action :discover do + unless exists? + Chef::Log.info("Discovering pear channel #{@new_resource}") + execute "pear channel-discover #{@new_resource.channel_name}" do + action :run + end + new_resource.updated_by_last_action(true) + end +end + +action :add do + unless exists? + Chef::Log.info("Adding pear channel #{@new_resource} from #{@new_resource.channel_xml}") + execute "pear channel-add #{@new_resource.channel_xml}" do + action :run + end + new_resource.updated_by_last_action(true) + end +end + +action :update do + if exists? + update_needed = false + begin + updated_needed = true if shell_out("pear search -c #{@new_resource.channel_name} NNNNNN").stdout =~ /channel-update/ + rescue Chef::Exceptions::CommandTimeout + # CentOS can hang on 'pear search' if a channel needs updating + Chef::Log.info("Timed out checking if channel-update needed...forcing update of pear channel #{@new_resource}") + update_needed = true + end + if update_needed + Chef::Log.info("Updating pear channel #{@new_resource}") + shell_out!("pear channel-update #{@new_resource.channel_name}") + new_resource.updated_by_last_action(true) + end + end +end + +action :remove do + if exists? + Chef::Log.info("Deleting pear channel #{@new_resource}") + execute "pear channel-delete #{@new_resource.channel_name}" do + action :run + end + new_resource.updated_by_last_action(true) + end +end + +def load_current_resource + @current_resource = Chef::Resource::PhpPearChannel.new(@new_resource.name) + @current_resource.channel_name(@new_resource.channel_name) + @current_resource +end + +private +def exists? + begin + shell_out!("pear channel-info #{@current_resource.channel_name}") + true + rescue Chef::Exceptions::ShellCommandFailed + false + rescue Mixlib::ShellOut::ShellCommandFailed + false + end +end diff --git a/recipes/PHPUnit.rb b/recipes/PHPUnit.rb index b205014..3147446 100755 --- a/recipes/PHPUnit.rb +++ b/recipes/PHPUnit.rb @@ -25,17 +25,17 @@ ] channels.each do |chan| - php_pear_channel chan do + chef_php_extra_pear_channel chan do action :discover end end -pu = php_pear_channel "pear.phpunit.de" do +pu = chef_php_extra_pear_channel "pear.phpunit.de" do action :discover end -php_pear "PHPUnit" do +chef_php_extra_pear "PHPUnit" do version "3.6.11" channel pu.channel_name action :install -end \ No newline at end of file +end diff --git a/recipes/PHP_CodeBrowser.rb b/recipes/PHP_CodeBrowser.rb index 5e86679..8f59d1b 100755 --- a/recipes/PHP_CodeBrowser.rb +++ b/recipes/PHP_CodeBrowser.rb @@ -26,17 +26,17 @@ ] channels.each do |chan| - php_pear_channel chan do + chef_php_extra_pear_channel chan do action :discover end end -pu = php_pear_channel "pear.phpunit.de" do +pu = chef_php_extra_pear_channel "pear.phpunit.de" do action :discover end -php_pear "PHP_CodeBrowser" do +chef_php_extra_pear "PHP_CodeBrowser" do version "1.0.2" channel pu.channel_name action :install -end \ No newline at end of file +end diff --git a/recipes/PHP_Depend.rb b/recipes/PHP_Depend.rb index 156d65b..381336a 100755 --- a/recipes/PHP_Depend.rb +++ b/recipes/PHP_Depend.rb @@ -19,12 +19,12 @@ include_recipe "chef-php-extra::pear" -pd = php_pear_channel "pear.pdepend.org" do +pd = chef_php_extra_pear_channel "pear.pdepend.org" do action :discover end -php_pear "PHP_Depend" do +chef_php_extra_pear "PHP_Depend" do version "1.0.7" channel pd.channel_name action :install -end \ No newline at end of file +end diff --git a/recipes/PHP_PMD.rb b/recipes/PHP_PMD.rb index c1997bb..75551be 100755 --- a/recipes/PHP_PMD.rb +++ b/recipes/PHP_PMD.rb @@ -24,16 +24,16 @@ ] channels.each do |chan| - php_pear_channel chan do + chef_php_extra_pear_channel chan do action :discover end end -pu = php_pear_channel "pear.phpmd.org" do +pu = chef_php_extra_pear_channel "pear.phpmd.org" do action :discover end -php_pear "PHP_PMD" do +chef_php_extra_pear "PHP_PMD" do version "1.3.3" channel pu.channel_name action :install diff --git a/recipes/phpcpd.rb b/recipes/phpcpd.rb index 02623ad..795a552 100755 --- a/recipes/phpcpd.rb +++ b/recipes/phpcpd.rb @@ -24,17 +24,17 @@ ] channels.each do |chan| - php_pear_channel chan do + chef_php_extra_pear_channel chan do action :discover end end -pu = php_pear_channel "pear.phpunit.de" do +pu = chef_php_extra_pear_channel "pear.phpunit.de" do action :discover end -php_pear "phpcpd" do +chef_php_extra_pear "phpcpd" do version "1.3.5" channel pu.channel_name action :install -end \ No newline at end of file +end diff --git a/recipes/phploc.rb b/recipes/phploc.rb index f25c48b..4dac717 100755 --- a/recipes/phploc.rb +++ b/recipes/phploc.rb @@ -24,17 +24,17 @@ ] channels.each do |chan| - php_pear_channel chan do + chef_php_extra_pear_channel chan do action :discover end end -pu = php_pear_channel "pear.phpunit.de" do +pu = chef_php_extra_pear_channel "pear.phpunit.de" do action :discover end -php_pear "phploc" do +chef_php_extra_pear "phploc" do version "1.6.4" channel pu.channel_name action :install -end \ No newline at end of file +end diff --git a/recipes/vfsStream.rb b/recipes/vfsStream.rb index 29e6655..14594a5 100755 --- a/recipes/vfsStream.rb +++ b/recipes/vfsStream.rb @@ -19,12 +19,12 @@ include_recipe "chef-php-extra::pear" -pat = php_pear_channel "pear.bovigo.org" do +pat = chef_php_extra_pear_channel "pear.bovigo.org" do action :discover end -php_pear "vfsStream" do +chef_php_extra_pear "vfsStream" do preferred_state "beta" channel pat.channel_name action :install -end \ No newline at end of file +end diff --git a/recipes/zendframework.rb b/recipes/zendframework.rb index c6248f2..d6829f2 100755 --- a/recipes/zendframework.rb +++ b/recipes/zendframework.rb @@ -1,11 +1,11 @@ include_recipe "chef-php-extra::pear" -zf = php_pear_channel "pear.zfcampus.org" do +zf = chef_php_extra_pear_channel "pear.zfcampus.org" do action :discover end -php_pear "zf" do +chef_php_extra_pear "zf" do preferred_state "stable" channel zf.channel_name action :install -end \ No newline at end of file +end diff --git a/resources/pear.rb b/resources/pear.rb new file mode 100644 index 0000000..d9e02e9 --- /dev/null +++ b/resources/pear.rb @@ -0,0 +1,29 @@ +# +# Author:: Seth Chisamore +# Cookbook Name:: php +# Resource:: pear_package +# +# Copyright:: 2011, Opscode, Inc +# +# 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. +# + +actions :install, :upgrade, :remove, :purge + +attribute :package_name, :kind_of => String, :name_attribute => true +attribute :version, :default => nil +attribute :channel, :kind_of => String +attribute :options, :kind_of => String +attribute :directives, :kind_of => Hash, :default => {} +attribute :zend_extensions, :kind_of => Array, :default => Array.new +attribute :preferred_state, :default => 'stable' diff --git a/resources/pear_channel.rb b/resources/pear_channel.rb new file mode 100644 index 0000000..af17c6f --- /dev/null +++ b/resources/pear_channel.rb @@ -0,0 +1,28 @@ +# +# Author:: Seth Chisamore +# Cookbook Name:: php +# Resource:: pear_channel +# +# Copyright:: 2011, Opscode, Inc +# +# 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. +# + +actions :discover, :add, :update, :remove + +attribute :channel_name, :kind_of => String, :name_attribute => true +attribute :channel_xml, :kind_of => String + +# TODO add authenticated channel support! +# attribute :username, :kind_of => String +# attribute :password, :kind_of => String \ No newline at end of file