require 'puppet/provider/package'
Puppet::Type.type(:package).provide :dpkg, :parent => Puppet::Provider::Package do
desc "Package management via `dpkg`. Because this only uses `dpkg`
and not `apt`, you must specify the source of any packages you want
to manage."
has_feature :holdable, :virtual_packages
commands :dpkg => "/usr/bin/dpkg"
commands :dpkg_deb => "/usr/bin/dpkg-deb"
commands :dpkgquery => "/usr/bin/dpkg-query"
# Performs a dpkgquery call with a pipe so that output can be processed
# inline in a passed block.
# @param args [Array<String>] any command line arguments to be appended to the command
# @param block expected to be passed on to execpipe
# @return whatever the block returns
# @see Puppet::Util::Execution.execpipe
# @api private
def self.dpkgquery_piped(*args, &block)
cmd = args.unshift(command(:dpkgquery))
Puppet::Util::Execution.execpipe(cmd, &block)
end
def self.instances
packages = []
# list out all of the packages
dpkgquery_piped('-W', '--showformat', self::DPKG_QUERY_FORMAT_STRING) do |pipe|
# now turn each returned line into a package object
pipe.each_line do |line|
hash = parse_line(line)
if hash
packages << new(hash)
end
end
end
packages
end
private
# Note: self:: is required here to keep these constants in the context of what will
# eventually become this Puppet::Type::Package::ProviderDpkg class.
self::DPKG_QUERY_FORMAT_STRING = %Q{'${Status} ${Package} ${Version}\\n'}
self::DPKG_QUERY_PROVIDES_FORMAT_STRING = %Q{'${Status} ${Package} ${Version} [${Provides}]\\n'}
self::FIELDS_REGEX = %r{^'?(\S+) +(\S+) +(\S+) (\S+) (\S*)$}
self::FIELDS_REGEX_WITH_PROVIDES = %r{^'?(\S+) +(\S+) +(\S+) (\S+) (\S*) \[.*\]$}
self::FIELDS= [:desired, :error, :status, :name, :ensure]
def self.defaultto_allow_virtual
false
end
# @param line [String] one line of dpkg-query output
# @return [Hash,nil] a hash of FIELDS or nil if we failed to match
# @api private
def self.parse_line(line, regex=self::FIELDS_REGEX)
hash = nil
match = regex.match(line)
if match
hash = {}
self::FIELDS.zip(match.captures) do |field,value|
hash[field] = value
end
hash[:provider] = self.name
if hash[:status] == 'not-installed'
hash[:ensure] = :purged
elsif ['config-files', 'half-installed', 'unpacked', 'half-configured'].include?(hash[:status])
hash[:ensure] = :absent
end
hash[:mark] = hash[:desired] == 'hold' ? :hold : :none
else
Puppet.debug("Failed to match dpkg-query line #{line.inspect}")
end
return hash
end
public
def install
file = @resource[:source]
unless file
raise ArgumentError, _("You cannot install dpkg packages without a source")
end
args = []
if @resource[:configfiles] == :keep
args << '--force-confold'
else
args << '--force-confnew'
end
args << '-i' << file
self.unhold if self.properties[:mark] == :hold
begin
dpkg(*args)
ensure
self.hold if @resource[:mark] == :hold
end
end
def update
self.install
end
# Return the version from the package.
def latest
source = @resource[:source]
unless source
@resource.fail _("Could not update: You cannot install dpkg packages without a source")
end
output = dpkg_deb "--show", source
matches = /^(\S+)\t(\S+)$/.match(output).captures
warning _("source doesn't contain named package, but %{name}") % { name: matches[0] } unless matches[0].match( Regexp.escape(@resource[:name]) )
matches[1]
end
def query
hash = nil
# list out our specific package
begin
if @resource.allow_virtual?
output = dpkgquery(
"-W",
"--showformat",
self.class::DPKG_QUERY_PROVIDES_FORMAT_STRING
#the regex searches for the resource[:name] in the dpkquery result in which the Provides field is also available
#it will search for the packages only in the brackets ex: [rubygems]
).lines.find {|package| package.match(/[\[ ](#{Regexp.escape(@resource[:name])})[\],]/)}
if output
hash = self.class.parse_line(output,self.class::FIELDS_REGEX_WITH_PROVIDES)
Puppet.info("Package #{@resource[:name]} is virtual, defaulting to #{hash[:name]}")
@resource[:name] = hash[:name]
end
end
output = dpkgquery(
"-W",
"--showformat",
self.class::DPKG_QUERY_FORMAT_STRING,
@resource[:name]
)
hash = self.class.parse_line(output)
rescue Puppet::ExecutionFailure
# dpkg-query exits 1 if the package is not found.
return {:ensure => :purged, :status => 'missing', :name => @resource[:name], :error => 'ok'}
end
hash ||= {:ensure => :absent, :status => 'missing', :name => @resource[:name], :error => 'ok'}
if hash[:error] != "ok"
raise Puppet::Error.new(
"Package #{hash[:name]}, version #{hash[:ensure]} is in error state: #{hash[:error]}"
)
end
hash
end
def uninstall
dpkg "-r", @resource[:name]
end
def purge
dpkg "--purge", @resource[:name]
end
def deprecated_hold
if package_not_installed?
self.install
end
hold
end
def hold
Tempfile.open('puppet_dpkg_set_selection') do |tmpfile|
tmpfile.write("#{@resource[:name]} hold\n")
tmpfile.flush
execute([:dpkg, "--set-selections"], :failonfail => false, :combine => false, :stdinfile => tmpfile.path.to_s)
end
end
def unhold
Tempfile.open('puppet_dpkg_set_selection') do |tmpfile|
tmpfile.write("#{@resource[:name]} install\n")
tmpfile.flush
execute([:dpkg, "--set-selections"], :failonfail => false, :combine => false, :stdinfile => tmpfile.path.to_s)
end
end
def package_not_installed?
query[:status] != "installed"
end
end
Copyright 2K16 - 2K18 Indonesian Hacker Rulez