File Manager
Viewing File: dpkg.rb
#
# Author:: Bryan McLellan (btm@loftninjas.org)
# Copyright:: Copyright 2009-2016, 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_relative "../package"
require_relative "deb"
require_relative "../../resource/package"
class Chef
class Provider
class Package
class Dpkg < Chef::Provider::Package
include Chef::Provider::Package::Deb
DPKG_REMOVED = /^Status: deinstall ok config-files/.freeze
DPKG_INSTALLED = /^Status: install ok installed/.freeze
DPKG_VERSION = /^Version: (.+)$/.freeze
provides :dpkg_package
use_multipackage_api
use_package_name_for_source
def define_resource_requirements
super
requirements.assert(:install, :upgrade) do |a|
a.assertion { !resolved_source_array.compact.empty? }
a.failure_message Chef::Exceptions::Package, "#{new_resource} the source property is required for action :install or :upgrade"
end
requirements.assert(:install, :upgrade) do |a|
a.assertion { source_files_exist? }
a.failure_message Chef::Exceptions::Package, "#{new_resource} source file(s) do not exist: #{missing_sources}"
a.whyrun "Assuming they would have been previously created."
end
end
def load_current_resource
@current_resource = Chef::Resource::Package.new(new_resource.name)
current_resource.package_name(new_resource.package_name)
if source_files_exist?
@candidate_version = get_candidate_version
current_resource.package_name(get_package_name)
# if the source file exists then our package_name is right
current_resource.version(get_current_version_from(current_package_name_array))
elsif !installing?
# we can't do this if we're installing with no source, because our package_name
# is probably not right.
#
# if we're removing or purging we don't use source, and our package_name must
# be right so we can do this.
#
# we don't error here on the dpkg command since we'll handle the exception or
# the why-run message in define_resource_requirements.
current_resource.version(get_current_version_from(current_package_name_array))
end
current_resource
end
def install_package(name, version)
sources = name.map { |n| name_sources[n] }
logger.info("#{new_resource} installing package(s): #{name.join(" ")}")
run_noninteractive("dpkg", "-i", *options, *sources)
end
def remove_package(name, version)
logger.info("#{new_resource} removing package(s): #{name.join(" ")}")
run_noninteractive("dpkg", "-r", *options, *name)
end
def purge_package(name, version)
logger.info("#{new_resource} purging packages(s): #{name.join(" ")}")
run_noninteractive("dpkg", "-P", *options, *name)
end
def upgrade_package(name, version)
install_package(name, version)
end
# Override the superclass check. Multiple sources are required here.
def check_resource_semantics!; end
private
# compare 2 versions to each other to see which is newer.
# this differs from the standard package method because we
# need to be able to parse debian version strings which contain
# tildes which Gem cannot properly parse
#
# @return [Integer] 1 if v1 > v2. 0 if they're equal. -1 if v1 < v2
def version_compare(v1, v2)
if !shell_out("dpkg", "--compare-versions", v1.to_s, "gt", v2.to_s).error?
1
elsif !shell_out("dpkg", "--compare-versions", v1.to_s, "eq", v2.to_s).error?
0
else
-1
end
end
def read_current_version_of_package(package_name)
logger.trace("#{new_resource} checking install state of #{package_name}")
status = shell_out!("dpkg", "-s", package_name, returns: [0, 1])
package_installed = false
status.stdout.each_line do |line|
case line
when DPKG_REMOVED
# if we are 'purging' then we consider 'removed' to be 'installed'
package_installed = true if action == :purge
when DPKG_INSTALLED
package_installed = true
when DPKG_VERSION
if package_installed
logger.trace("#{new_resource} current version is #{$1}")
return $1
end
end
end
nil
end
def get_current_version_from(array)
array.map do |name|
read_current_version_of_package(name)
end
end
# Returns true if all sources exist. Returns false if any do not, or if no
# sources were specified.
#
# @return [Boolean] True if all sources exist
def source_files_exist?
resolved_source_array.all? { |s| s && ::File.exist?(s) }
end
# Helper to return all the names of the missing sources for error messages.
#
# @return [Array<String>] Array of missing sources
def missing_sources
resolved_source_array.select { |s| s.nil? || !::File.exist?(s) }
end
def current_package_name_array
[ current_resource.package_name ].flatten
end
# Helper to construct Hash of names-to-sources.
#
# @return [Hash] Mapping of package names to sources
def name_sources
@name_sources =
begin
Hash[*package_name_array.zip(resolved_source_array).flatten]
end
end
# Helper to construct Hash of names-to-package-information.
#
# @return [Hash] Mapping of package names to package information
def name_pkginfo
@name_pkginfo ||=
begin
pkginfos = resolved_source_array.map do |src|
logger.trace("#{new_resource} checking #{src} dpkg status")
status = shell_out!("dpkg-deb", "-W", src)
status.stdout
end
Hash[*package_name_array.zip(pkginfos).flatten]
end
end
def name_candidate_version
@name_candidate_version ||=
begin
Hash[name_pkginfo.map { |k, v| [k, v ? v.split("\t")[1].strip : nil] }]
end
end
def name_package_name
@name_package_name ||=
begin
Hash[name_pkginfo.map { |k, v| [k, v ? v.split("\t")[0] : nil] }]
end
end
# Return candidate version array from pkg-deb -W against the source file(s).
#
# @return [Array] Array of candidate versions read from the source files
def get_candidate_version
package_name_array.map { |name| name_candidate_version[name] }
end
# Return package names from the candidate source file(s).
#
# @return [Array] Array of actual package names read from the source files
def get_package_name
package_name_array.map { |name| name_package_name[name] }
end
# Since upgrade just calls install, this is a helper to determine
# if our action means that we'll be calling install_package.
#
# @return [Boolean] true if we're doing :install or :upgrade
def installing?
%i{install upgrade}.include?(action)
end
end
end
end
end