Download raw body.
Overhaul package handling in puppet 8
On Tue, Apr 09, 2024 at 10:17:30PM +0200, Sebastian Reitenbach wrote:
> Since we now have a recent Puppet in ports, I started looking at how packages are handled with Puppet.
> My current trouble is that it wasn't really possible to install banches properly: i.e. can't properly install gimp,
> or auto* based on branch. Or esp, when want to install multiple of them, it was just not possible.
> For ports where branches conflict, i.e. postfix, this was working, but had to specify exact version, and on every
> upgrade bump the version .... _very_ annoying.
>
> Currently Puppet allows to install packages of a given version (ensure => "X.Y.Z"), or to follow updates (ensure => "latest").
>
[...]
> This is just for Puppet 8. Anyone still on Puppet 7? It should be easily ported to Puppet 7 as well.
>
I am on Puppet 7 and I do not have time to upgrade to Puppet 8 soon; I
am also using "ensure => latest" syntax but I can change my code to get
rid of it OpenBSD will no more support it.
Cheers
Giovanni
> cheers,
> Sebastian
>
>
> Index: Makefile
> ===================================================================
> RCS file: /cvs/ports/sysutils/ruby-puppet/8/Makefile,v
> diff -u -r1.2 Makefile
> --- Makefile 20 Mar 2024 21:21:14 -0000 1.2
> +++ Makefile 9 Apr 2024 19:51:40 -0000
> @@ -1,6 +1,7 @@
> PORTROACH= limit:^7
>
> VERSION= 8.5.1
> +REVISION= 0
>
> RUN_DEPENDS+= converters/ruby-multi_json,${MODRUBY_FLAVOR}>=1.13,<2 \
> devel/ruby-concurrent-ruby,${MODRUBY_FLAVOR}>=1,<2 \
> Index: patches/patch-lib_puppet_provider_package_openbsd_rb
> ===================================================================
> RCS file: /cvs/ports/sysutils/ruby-puppet/8/patches/patch-lib_puppet_provider_package_openbsd_rb,v
> diff -u -r1.2 patch-lib_puppet_provider_package_openbsd_rb
> --- patches/patch-lib_puppet_provider_package_openbsd_rb 20 Mar 2024 21:21:14 -0000 1.2
> +++ patches/patch-lib_puppet_provider_package_openbsd_rb 9 Apr 2024 19:51:40 -0000
> @@ -1,42 +1,89 @@
> -- Handle errors from pkg_add
> -- Handle uninstall_options being 'nil' by default
> -- If no flavor speficied, force the empty flavor with '--'
> - but skipping the % un-ambiguity pkg names
> -- Bail out on shortform PKG_PATH (i.e. 'ftp.openbsd.org')
> -- pkg.conf is gone
> -- properly handle packages with multiple versions and flavors,
> - i.e. postfix-XXX-flavor
> -
> +- get rid of versionable (no ensure => "version X.X.X")
> +- get rid of upgradeable (ensure => latest)
> +- properly support branches
>
> Index: lib/puppet/provider/package/openbsd.rb
> --- lib/puppet/provider/package/openbsd.rb.orig
> +++ lib/puppet/provider/package/openbsd.rb
> -@@ -24,6 +24,8 @@ Puppet::Type.type(:package).provide :openbsd, :parent
> - has_feature :upgradeable
> +@@ -6,10 +6,14 @@ require_relative '../../../puppet/provider/package'
> + Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Package do
> + desc "OpenBSD's form of `pkg_add` support.
> +
> ++ OpenBSD has the concept of package branches, providing multiple versions of the
> ++ same package, i.e. `stable` vs. `snapshot`. To select a specific branch,
> ++ suffix the package name with % sign follwed by the branch name, i.e. `gimp%stable`.
> ++
> + This provider supports the `install_options` and `uninstall_options`
> + attributes, which allow command-line flags to be passed to pkg_add and pkg_delete.
> + These options should be specified as an array where each element is either a
> +- string or a hash."
> ++ string or a hash."
> +
> + commands :pkginfo => "pkg_info",
> + :pkgadd => "pkg_add",
> +@@ -18,220 +22,94 @@ Puppet::Type.type(:package).provide :openbsd, :parent
> + defaultfor 'os.name' => :openbsd
> + confine 'os.name' => :openbsd
> +
> +- has_feature :versionable
> + has_feature :install_options
> + has_feature :uninstall_options
> +- has_feature :upgradeable
> has_feature :supports_flavors
>
> -+ mk_resource_methods
> -+
> def self.instances
> - packages = []
> -
> -@@ -46,12 +48,6 @@ Puppet::Type.type(:package).provide :openbsd, :parent
> -
> - packages << new(hash)
> - hash = {}
> +- packages = []
> +-
> ++ final = []
> + begin
> +- execpipe(listcmd) do |process|
> +- # our regex for matching pkg_info output
> +- regex = /^(.*)-(\d[^-]*)[-]?([\w-]*)(.*)$/
> +- fields = [:name, :ensure, :flavor]
> +- hash = {}
> ++ packages = listcmd
> ++ packages.each { |package, value|
> ++ if !package.empty?()
> ++ value[:provider] = self.name
> ++ final << new(value)
> ++ end
> ++ }
> ++ return final
> +
> +- # now turn each returned line into a package object
> +- process.each_line { |line|
> +- match = regex.match(line.split[0])
> +- if match
> +- fields.zip(match.captures) { |field, value|
> +- hash[field] = value
> +- }
> +-
> +- hash[:provider] = self.name
> +-
> +- packages << new(hash)
> +- hash = {}
> - else
> - unless line =~ /Updating the pkgdb/
> - # Print a warning on lines we can't match, but move
> - # on, since it should be non-fatal
> - warning(_("Failed to match line %{line}") % { line: line })
> - end
> - end
> - }
> - end
> -@@ -67,26 +63,17 @@ Puppet::Type.type(:package).provide :openbsd, :parent
> +- end
> +- }
> +- end
> +-
> +- return packages
> + rescue Puppet::ExecutionFailure
> +- return nil
> ++ nil
> + end
> end
>
> - def latest
> + def self.listcmd
> +- [command(:pkginfo), "-a"]
> +- end
> +-
> +- def latest
> - parse_pkgconf
> -
> - if @resource[:source][-1, 1] == ::File::SEPARATOR
> @@ -45,55 +92,51 @@
> - e_vars = {}
> - end
> -
> - if @resource[:flavor]
> - query = "#{@resource[:name]}--#{@resource[:flavor]}"
> - else
> +- if @resource[:flavor]
> +- query = "#{@resource[:name]}--#{@resource[:flavor]}"
> +- else
> - query = @resource[:name]
> -+ query = @resource[:name] + "--"
> - end
> -
> +- end
> +-
> - output = Puppet::Util.withenv(e_vars) { pkginfo "-Q", query }
> - version = properties[:ensure]
> -+ output = Puppet::Util.withenv({}) {pkginfo "-Q", query}
> -
> - if output.nil? or output.size == 0 or output =~ /Error from /
> - debug "Failed to query for #{resource[:name]}"
> +-
> +- if output.nil? or output.size == 0 or output =~ /Error from /
> +- debug "Failed to query for #{resource[:name]}"
> - return version
> -+ return properties[:ensure]
> - else
> - # Remove all fuzzy matches first.
> - output = output.split.select { |p| p =~ /^#{resource[:name]}-(\d[^-]*)[-]?(\w*)/ }.join
> -@@ -95,21 +82,22 @@ Puppet::Type.type(:package).provide :openbsd, :parent
> -
> - if output =~ /^#{resource[:name]}-(\d[^-]*)[-]?(\w*) \(installed\)$/
> - debug "Package is already the latest available"
> +- else
> +- # Remove all fuzzy matches first.
> +- output = output.split.select { |p| p =~ /^#{resource[:name]}-(\d[^-]*)[-]?(\w*)/ }.join
> +- debug "pkg_info -Q for #{resource[:name]}: #{output}"
> +- end
> +-
> +- if output =~ /^#{resource[:name]}-(\d[^-]*)[-]?(\w*) \(installed\)$/
> +- debug "Package is already the latest available"
> - return version
> -+ return properties[:ensure]
> - else
> - match = /^(.*)-(\d[^-]*)[-]?(\w*)$/.match(output)
> - debug "Latest available for #{resource[:name]}: #{match[2]}"
> -
> +- else
> +- match = /^(.*)-(\d[^-]*)[-]?(\w*)$/.match(output)
> +- debug "Latest available for #{resource[:name]}: #{match[2]}"
> +-
> - if version.to_sym == :absent || version.to_sym == :purged
> -+ if properties[:ensure].to_sym == :absent
> - return match[2]
> - end
> -
> - vcmp = version.split('.').map { |s| s.to_i } <=> match[2].split('.').map { |s| s.to_i }
> -+ vcmp = properties[:ensure].split('.').map{|s|s.to_i} <=> match[2].split('.').map{|s|s.to_i}
> - if vcmp > 0
> - # The locally installed package may actually be newer than what a mirror
> - # has. Log it at debug, but ignore it otherwise.
> +- return match[2]
> +- end
> +-
> +- vcmp = version.split('.').map { |s| s.to_i } <=> match[2].split('.').map { |s| s.to_i }
> +- if vcmp > 0
> +- # The locally installed package may actually be newer than what a mirror
> +- # has. Log it at debug, but ignore it otherwise.
> - debug "Package #{resource[:name]} #{version} newer then available #{match[2]}"
> - return version
> -+ debug "Package #{resource[:name]} #{properties[:ensure]} newer then available #{match[2]}"
> -+ return properties[:ensure]
> - else
> - return match[2]
> - end
> -@@ -120,57 +108,25 @@ Puppet::Type.type(:package).provide :openbsd, :parent
> - self.install(true)
> - end
> -
> +- else
> +- return match[2]
> +- end
> +- end
> +- end
> +-
> +- def update
> +- self.install(true)
> +- end
> +-
> - def parse_pkgconf
> - unless @resource[:source]
> - if Puppet::FileSystem.exist?("/etc/pkg.conf")
> @@ -111,7 +154,20 @@
> - end
> - end
> - end
> -- end
> ++ regex_fuzzy = /^(.*)--([\w-]+)?(%[^w]+)?$/
> ++ f = []
> ++ f = pkginfo("-a", "-z").split("\n")
> ++ packages = {}
> ++ f.each do |line|
> ++ match = regex_fuzzy.match(line.split[0])
> ++ name = match.captures[0]
> ++ flavor = match.captures[1]
> ++ branch = match.captures[2]
> ++ if branch.nil?
> ++ pname = name
> ++ else
> ++ pname = name + branch
> + end
> -
> - unless @resource[:source]
> - raise Puppet::Error,
> @@ -121,14 +177,18 @@
> - raise Puppet::Error,
> - _("You must specify a package source or configure an installpath in /etc/pkg.conf")
> - end
> -- end
> -- end
> --
> - def install(latest = false)
> ++ packages[pname] = { :name => pname, :flavor => flavor, :branch => branch, :ensure => "present" }
> + end
> ++ packages
> + end
> +
> +- def install(latest = false)
> ++ def install
> cmd = []
>
> - parse_pkgconf
> --
> ++ full_name = get_full_name(action="install")
> +
> - if @resource[:source][-1, 1] == ::File::SEPARATOR
> - e_vars = { 'PKG_PATH' => @resource[:source] }
> - full_name = get_full_name(latest)
> @@ -139,27 +199,24 @@
> -
> + cmd << '-r'
> cmd << install_options
> -- cmd << full_name
> -+ cmd << get_full_name(latest)
> + cmd << full_name
>
> - if latest
> +- if latest
> - cmd.unshift('-rz')
> -+ cmd.unshift('-z')
> - end
> -
> -- Puppet::Util.withenv(e_vars) { pkgadd cmd.flatten.compact }
> + # pkg_add(1) doesn't set the return value upon failure so we have to peek
> + # at it's output to see if something went wrong.
> + output = Puppet::Util.withenv({}) { pkgadd cmd.flatten.compact }
> -+ require 'pp'
> -+ pp output
> + if output =~ /Can't find /
> + self.fail "pkg_add returned: #{output.chomp}"
> -+ end
> + end
> +-
> +- Puppet::Util.withenv(e_vars) { pkgadd cmd.flatten.compact }
> end
>
> - def get_full_name(latest = false)
> -@@ -179,11 +135,20 @@ Puppet::Type.type(:package).provide :openbsd, :parent
> +- def get_full_name(latest = false)
> ++ def get_full_name(action="install")
> + # In case of a real update (i.e., the package already exists) then
> + # pkg_add(8) can handle the flavors. However, if we're actually
> # installing with 'latest', we do need to handle the flavors. This is
> # done so we can feed pkg_add(8) the full package name to install to
> # prevent ambiguity.
> @@ -168,53 +225,35 @@
> - elsif latest
> - # Don't depend on get_version for updates.
> - @resource[:name]
> -+ if resource[:flavor]
> -+ # If :ensure contains a version, use that instead of looking it up.
> -+ # This allows for installing packages with the same stem, but multiple
> -+ # version such as postfix-VERSION-flavor.
> -+ if @resource[:ensure].to_s =~ /(\d[^-]*)$/
> -+ use_version = @resource[:ensure]
> -+ else
> -+ use_version = ''
> -+ end
> -+ "#{resource[:name]}-#{use_version}-#{resource[:flavor]}"
> -+ elsif resource[:name].to_s.match(/[a-z0-9]%[0-9a-z]/i)
> -+ "#{resource[:name]}"
> -+ elsif not latest
> -+ "#{resource[:name]}--"
> - else
> - # If :ensure contains a version, use that instead of looking it up.
> - # This allows for installing packages with the same stem, but multiple
> -@@ -194,33 +159,41 @@ Puppet::Type.type(:package).provide :openbsd, :parent
> - use_version = get_version
> - end
> +- else
> +- # If :ensure contains a version, use that instead of looking it up.
> +- # This allows for installing packages with the same stem, but multiple
> +- # version such as openldap-server.
> +- if @resource[:ensure].to_s =~ /(\d[^-]*)$/
> +- use_version = @resource[:ensure]
> +- else
> +- use_version = get_version
> +- end
>
> - [@resource[:name], use_version, @resource[:flavor]].join('-').gsub(/-+$/, '')
> -+ if resource[:flavor]
> -+ [ @resource[:name], use_version, @resource[:flavor]].join('-').gsub(/-+$/, '')
> -+ else
> -+ [ @resource[:name], use_version ]
> -+ end
> ++ name_branch_regex = /^(\S*)(%\w*)$/
> ++ match = name_branch_regex.match(@resource[:name])
> ++ if match
> ++ use_name = match.captures[0]
> ++ use_branch = match.captures[1]
> ++ else
> ++ use_name = @resource[:name]
> ++ use_branch = ''
> end
> - end
> +- end
>
> - def get_version
> +- def get_version
> - execpipe([command(:pkginfo), "-I", @resource[:name]]) do |process|
> - # our regex for matching pkg_info output
> - regex = /^(.*)-(\d[^-]*)[-]?(\w*)(.*)$/
> - master_version = 0
> - version = -1
> -+ pkg_search_name = @resource[:name]
> -+ unless pkg_search_name.match(/[a-z0-9]%[0-9a-z]/i) and not @resource[:flavor]
> -+ # we are only called when no flavor is specified
> -+ # so append '--' to the :name to avoid patch versions on flavors
> -+ pkg_search_name << "--"
> -+ end
> -+ # our regex for matching pkg_info output
> -+ regex = /^(.*)-(\d[^-]*)[-]?(\w*)(.*)$/
> -+ master_version = 0
> -+ version = -1
> -
> +-
> - process.each_line do |line|
> - match = regex.match(line.split[0])
> - if match
> @@ -224,29 +263,36 @@
> -
> - master_version = version unless master_version > version
> - end
> -+ # pkg_info -I might return multiple lines, i.e. flavors
> -+ matching_pkgs = pkginfo("-I", "pkg_search_name")
> -+ matching_pkgs.each_line do |line|
> -+ if match = regex.match(line.split[0])
> -+ # now we return the first version, unless ensure is latest
> -+ version = match.captures[1]
> -+ return version unless @resource[:ensure] == "latest"
> -+ master_version = version unless master_version > version
> - end
> -+ end
> -
> +- end
> +-
> - return master_version unless master_version == 0
> - return '' if version == -1
> -+ return master_version unless master_version == 0
> -+ return '' if version == -1
> -+ raise Puppet::Error, _("%{version} is not available for this package") % { version: version }
> -
> +-
> - raise Puppet::Error, _("%{version} is not available for this package") % { version: version }
> -- end
> - rescue Puppet::ExecutionFailure
> - return nil
> ++ if @resource[:flavor]
> ++ return "#{use_name}--#{@resource[:flavor]}#{use_branch}"
> ++ else
> ++ return "#{use_name}--#{use_branch}"
> + end
> +- rescue Puppet::ExecutionFailure
> +- return nil
> ++
> end
> -@@ -239,7 +212,7 @@ Puppet::Type.type(:package).provide :openbsd, :parent
> +
> + def query
> +- # Search for the version info
> +- if pkginfo(@resource[:name]) =~ /Information for (inst:)?#{@resource[:name]}-(\S+)/
> +- return { :ensure => $2 }
> +- else
> +- return nil
> ++ pkg = self.class.instances.find do |package|
> ++ @resource[:name] == package.name
> + end
> ++ pkg ? pkg.properties : nil
> + end
> +
> + def install_options
> +@@ -239,15 +117,19 @@ Puppet::Type.type(:package).provide :openbsd, :parent
> end
>
> def uninstall_options
> @@ -254,4 +300,27 @@
> + [join_options(resource[:uninstall_options])]
> end
>
> - def uninstall
> +- def uninstall
> +- pkgdelete uninstall_options.flatten.compact, @resource[:name]
> ++ def uninstall(purge = false)
> ++ if purge
> ++ pkgdelete "-c", "-qq", uninstall_options.flatten.compact, get_full_name(action="uninstall")
> ++ else
> ++ pkgdelete uninstall_options.flatten.compact, get_full_name(action="uninstall")
> ++ end
> + end
> +
> + def purge
> +- pkgdelete "-c", "-q", @resource[:name]
> ++ uninstall(purge=true)
> + end
> +
> + def flavor
> +@@ -256,7 +138,6 @@ Puppet::Type.type(:package).provide :openbsd, :parent
> +
> + def flavor=(value)
> + if flavor != @resource.should(:flavor)
> +- uninstall
> + install
> + end
> + end
Overhaul package handling in puppet 8