From d8f933bd00a5cc416da00cd26c9d13f7a1c02486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isma=C3=ABl=20Bouya?= Date: Sun, 1 Jul 2018 15:35:43 +0200 Subject: Add monitoring --- .../lib/puppet/provider/package/pacman.rb | 283 +++++++++++++++++++++ .../lib/puppet/provider/package/pip2.rb | 17 ++ .../manifests/package_managers.pp | 6 + 3 files changed, 306 insertions(+) create mode 100644 modules/base_installation/lib/puppet/provider/package/pacman.rb create mode 100644 modules/base_installation/lib/puppet/provider/package/pip2.rb (limited to 'modules/base_installation') diff --git a/modules/base_installation/lib/puppet/provider/package/pacman.rb b/modules/base_installation/lib/puppet/provider/package/pacman.rb new file mode 100644 index 0000000..0a5e5d0 --- /dev/null +++ b/modules/base_installation/lib/puppet/provider/package/pacman.rb @@ -0,0 +1,283 @@ +require 'puppet/provider/package' +require 'set' +require 'uri' + +Puppet::Type.type(:package).provide :pacman, :parent => Puppet::Provider::Package do + desc "Support for the Package Manager Utility (pacman) used in Archlinux. + + This provider supports the `install_options` attribute, which allows command-line flags to be passed to pacman. + These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}), + or an array where each element is either a string or a hash." + + # If aura is installed, we can make use of it + def self.aura? + @aura ||= Puppet::FileSystem.exist?('/usr/bin/aura') + end + + commands :pacman => "/usr/bin/pacman" + # Aura is a common AUR helper which, if installed, we can use to query the AUR + commands :aura => "/usr/bin/aura" if aura? + + confine :operatingsystem => [:archlinux, :manjarolinux] + defaultfor :operatingsystem => [:archlinux, :manjarolinux] + has_feature :install_options + has_feature :uninstall_options + has_feature :upgradeable + has_feature :virtual_packages + + # Checks if a given name is a group + def self.group?(name) + begin + !pacman("-Sg", name).empty? + rescue Puppet::ExecutionFailure + # pacman returns an expected non-zero exit code when the name is not a group + false + end + end + + # Install a package using 'pacman', or 'aura' if available. + # Installs quietly, without confirmation or progress bar, updates package + # list from servers defined in pacman.conf. + def install + if @resource[:source] + install_from_file + else + install_from_repo + end + + unless self.query + fail(_("Could not find package '%{name}'") % { name: @resource[:name] }) + end + end + + # Fetch the list of packages and package groups that are currently installed on the system. + # Only package groups that are fully installed are included. If a group adds packages over time, it will not + # be considered as fully installed any more, and we would install the new packages on the next run. + # If a group removes packages over time, nothing will happen. This is intended. + def self.instances + instances = [] + + # Get the installed packages + installed_packages = get_installed_packages + installed_packages.sort_by { |k, _| k }.each do |package, version| + instances << new(to_resource_hash(package, version)) + end + + # Get the installed groups + get_installed_groups(installed_packages).each do |group, version| + instances << new(to_resource_hash(group, version)) + end + + instances + end + + # returns a hash package => version of installed packages + def self.get_installed_packages + begin + packages = {} + execpipe([command(:pacman), "-Q"]) do |pipe| + # pacman -Q output is 'packagename version-rel' + regex = %r{^(\S+)\s(\S+)} + pipe.each_line do |line| + if match = regex.match(line) + packages[match.captures[0]] = match.captures[1] + else + warning(_("Failed to match line '%{line}'") % { line: line }) + end + end + end + packages + rescue Puppet::ExecutionFailure + fail(_("Error getting installed packages")) + end + end + + # returns a hash of group => version of installed groups + def self.get_installed_groups(installed_packages, filter = nil) + groups = {} + begin + # Build a hash of group name => list of packages + command = [command(:pacman), "-Sgg"] + command << filter if filter + execpipe(command) do |pipe| + pipe.each_line do |line| + name, package = line.split + packages = (groups[name] ||= []) + packages << package + end + end + + # Remove any group that doesn't have all its packages installed + groups.delete_if do |_, packages| + !packages.all? { |package| installed_packages[package] } + end + + # Replace the list of packages with a version string consisting of packages that make up the group + groups.each do |name, packages| + groups[name] = packages.sort.map {|package| "#{package} #{installed_packages[package]}"}.join ', ' + end + rescue Puppet::ExecutionFailure + # pacman returns an expected non-zero exit code when the filter name is not a group + raise unless filter + end + groups + end + + # Because Archlinux is a rolling release based distro, installing a package + # should always result in the newest release. + def update + # Install in pacman can be used for update, too + self.install + end + + # We rescue the main check from Pacman with a check on the AUR using aura, if installed + def latest + # Synchronize the database + pacman "-Sy" + + resource_name = @resource[:name] + + # If target is a group, construct the group version + return pacman("-Sp", "--print-format", "%n %v", resource_name).lines.map{ |line| line.chomp }.sort.join(', ') if self.class.group?(resource_name) + + # Start by querying with pacman first + # If that fails, retry using aura against the AUR + pacman_check = true + begin + if pacman_check + output = pacman "-Sp", "--print-format", "%v", resource_name + return output.chomp + else + output = aura "-Ai", resource_name + output.split("\n").each do |line| + return line.split[2].chomp if line.split[0] =~ /Version/ + end + end + rescue Puppet::ExecutionFailure + if pacman_check and self.class.aura? + pacman_check = false # now try the AUR + retry + else + raise + end + end + end + + # Queries information for a package or package group + def query + installed_packages = self.class.get_installed_packages + resource_name = @resource[:name] + + # Check for the resource being a group + version = self.class.get_installed_groups(installed_packages, resource_name)[resource_name] + + if version + unless @resource.allow_virtual? + warning(_("%{resource_name} is a group, but allow_virtual is false.") % { resource_name: resource_name }) + return nil + end + else + version = installed_packages[resource_name] + end + + # Return nil if no package or group found + return nil unless version + + self.class.to_resource_hash(resource_name, version) + end + + def self.to_resource_hash(name, version) + { + :name => name, + :ensure => version, + :provider => self.name + } + end + + # Removes a package from the system. + def uninstall + resource_name = @resource[:name] + + is_group = self.class.group?(resource_name) + + fail(_("Refusing to uninstall package group %{resource_name}, because allow_virtual is false.") % { resource_name: resource_name }) if is_group && !@resource.allow_virtual? + + cmd = %w{--noconfirm --noprogressbar} + cmd += uninstall_options if @resource[:uninstall_options] + cmd << "-R" + cmd << '-s' if is_group + cmd << resource_name + + if self.class.aura? + aura(*cmd) + else + pacman(*cmd) + end + end + + private + + def install_with_aura? + resource_name = @resource[:name] + if !self.class.aura? + return false + end + + begin + pacman "-Sp", resource_name + return false + rescue Puppet::ExecutionFailure + return true + end + end + + def install_options + join_options(@resource[:install_options]) + end + + def uninstall_options + join_options(@resource[:uninstall_options]) + end + + def install_from_file + source = @resource[:source] + begin + source_uri = URI.parse source + rescue => detail + self.fail Puppet::Error, _("Invalid source '%{source}': %{detail}") % { source: source, detail: detail }, detail + end + + source = case source_uri.scheme + when nil then source + when /https?/i then source + when /ftp/i then source + when /file/i then source_uri.path + when /puppet/i + fail _("puppet:// URL is not supported by pacman") + else + fail _("Source %{source} is not supported by pacman") % { source: source } + end + pacman "--noconfirm", "--noprogressbar", "-Sy" + pacman "--noconfirm", "--noprogressbar", "-U", source + end + + def install_from_repo + resource_name = @resource[:name] + + # Refuse to install if not allowing virtual packages and the resource is a group + fail(_("Refusing to install package group %{resource_name}, because allow_virtual is false.") % { resource_name: resource_name }) if self.class.group?(resource_name) && !@resource.allow_virtual? + + cmd = %w{--noconfirm --needed} + cmd += install_options if @resource[:install_options] + + if install_with_aura? + cmd << "-Aq" << resource_name + aura(*cmd) + else + cmd << "--noprogressbar" + cmd << "-Sy" << resource_name + pacman(*cmd) + end + end + +end diff --git a/modules/base_installation/lib/puppet/provider/package/pip2.rb b/modules/base_installation/lib/puppet/provider/package/pip2.rb new file mode 100644 index 0000000..27cc0c4 --- /dev/null +++ b/modules/base_installation/lib/puppet/provider/package/pip2.rb @@ -0,0 +1,17 @@ +require 'puppet/provider/package/pip' + +Puppet::Type.type(:package).provide :pip2, + :parent => :pip do + + desc "Python packages via `pip2`. + + This provider supports the `install_options` attribute, which allows command-line flags to be passed to pip2. + These options should be specified as a string (e.g. '--flag'), a hash (e.g. {'--flag' => 'value'}), + or an array where each element is either a string or a hash." + + has_feature :installable, :uninstallable, :upgradeable, :versionable, :install_options + + def self.cmd + ["pip2"] + end +end diff --git a/modules/base_installation/manifests/package_managers.pp b/modules/base_installation/manifests/package_managers.pp index c5c8485..a03085d 100644 --- a/modules/base_installation/manifests/package_managers.pp +++ b/modules/base_installation/manifests/package_managers.pp @@ -18,6 +18,12 @@ class base_installation::package_managers inherits base_installation { include => '/etc/pacman.d/mirrorlist' } + pacman::repo { 'immae': + order => 0, + server => 'https://git.immae.eu/releases/packages/', + siglevel => 'Optional' + } + class { 'aur': } contain "pacman" -- cgit v1.2.3