# dnfmodule - A puppet package provider for DNF modules
#
# Installing a module:
# package { 'postgresql':
# provider => 'dnfmodule',
# ensure => '9.6', # install a specific stream
# flavor => 'client', # install a specific profile
# }
require 'puppet/provider/package'
Puppet::Type.type(:package).provide :dnfmodule, :parent => :dnf do
has_feature :installable, :uninstallable, :versionable, :supports_flavors, :disableable
#has_feature :upgradeable
# it's not (yet) feasible to make this upgradeable since module streams don't
# always have matching version types (i.e. idm has streams DL1 and client,
# other modules have semver streams, others have string streams... we cannot
# programatically determine a latest version for ensure => 'latest'
commands :dnf => '/usr/bin/dnf'
def self.current_version
@current_version ||= dnf('--version').split.first
end
def self.prefetch(packages)
if Puppet::Util::Package.versioncmp(current_version, '3.0.1') < 0
raise Puppet::Error, _("Modules are not supported on DNF versions lower than 3.0.1")
end
super
end
def self.instances
packages = []
cmd = "#{command(:dnf)} module list -d 0 -e #{error_level}"
execute(cmd).each_line do |line|
# select only lines with actual packages since DNF clutters the output
next unless line =~ /\[[eix]\][, ]/
line.gsub!(/\[d\]/, '') # we don't care about the default flag
flavor = if line.include?('[i]')
line.split('[i]').first.split.last
else
:absent
end
packages << new(
name: line.split[0],
ensure: if line.include?('[x]')
:disabled
else
line.split[1]
end,
flavor: flavor,
provider: name
)
end
packages
end
def query
pkg = self.class.instances.find do |package|
@resource[:name] == package.name
end
pkg ? pkg.properties : nil
end
# to install specific streams and profiles:
# $ dnf module install module-name:stream/profile
# $ dnf module install perl:5.24/minimal
# if unspecified, they will be defaulted (see [d] param in dnf module list output)
def install
# ensure we start fresh (remove existing stream)
uninstall unless [:absent, :purged].include?(@property_hash[:ensure])
args = @resource[:name].dup
case @resource[:ensure]
when true, false, Symbol
# pass
else
args << ":#{@resource[:ensure]}"
end
args << "/#{@resource[:flavor]}" if @resource[:flavor]
if @resource[:enable_only] == true
enable(args)
else
begin
execute([command(:dnf), 'module', 'install', '-d', '0', '-e', self.class.error_level, '-y', args])
rescue Puppet::ExecutionFailure => e
# module has no default profile and no profile was requested, so just enable the stream
# DNF versions prior to 4.2.8 do not need this workaround
# see https://bugzilla.redhat.com/show_bug.cgi?id=1669527
if @resource[:flavor] == nil && e.message =~ /^(?:missing|broken) groups or modules: #{Regexp.quote(@resource[:name])}$/
enable(args)
else
raise
end
end
end
end
# should only get here when @resource[ensure] is :disabled
def insync?(is)
if resource[:ensure] == :disabled
# in sync only if package is already disabled
pkg = self.class.instances.find do |package|
@resource[:name] == package.name && package.properties[:ensure] == :disabled
end
return true if pkg
end
return false
end
def enable(args = @resource[:name])
execute([command(:dnf), 'module', 'enable', '-d', '0', '-e', self.class.error_level, '-y', args])
end
def uninstall
execute([command(:dnf), 'module', 'remove', '-d', '0', '-e', self.class.error_level, '-y', @resource[:name]])
reset # reset module to the default stream
end
def disable(args = @resource[:name])
execute([command(:dnf), 'module', 'disable', '-d', '0', '-e', self.class.error_level, '-y', args])
end
def reset
execute([command(:dnf), 'module', 'reset', '-d', '0', '-e', self.class.error_level, '-y', @resource[:name]])
end
def flavor
@property_hash[:flavor]
end
def flavor=(value)
install if flavor != @resource.should(:flavor)
end
end
Copyright 2K16 - 2K18 Indonesian Hacker Rulez