OmniOS - Broken Puppet
23 June 2021

I was deploying some OmniOS zones, using a mechanism I’ve used a thousand times before, and the initial Puppet runs all failed with

Info: Loading facts
Info: Caching catalog for cube-mariadb.localnet
Info: Applying configuration version '1624443968'
Error: Could not prefetch package provider 'pkg': Unknown format pkg: im-[m]
Error: Failed to apply catalog: Unknown format pkg: im-[m]

This being Puppet, the error message isn’t the clearest, and running the agent with --debug didn’t show me anything obvious.

But it’s Ruby, so it’s easy to grep for the error message on the failing system, and a couple of minutes of printf debugging later, I’d tracked it down to this block

case flags[1..1]
    when 'f'
      {:mark => :hold}
    when '-'
      {}
    else
      raise ArgumentError, _('Unknown format %{resource_name}: %{full_flags}[%{bad_flag}]') %
          { resource_name: self.name, full_flags: flags, bad_flag: flags[1..1] }
    end

At this point Puppet is inspecting the IFO column in the output of pkg list, and it expects the second character of that column to be an f or a -. But in the errant OmniOS zone, I see stuff like this:

NAME (PUBLISHER)                                  VERSION                    IFO
ooce/database/mariadb-105 (extra.omnios)          10.5.10-151038.0           im-
ooce/runtime/ruby-26 (extra.omnios)               2.6.7-151038.0             im-

I vaguely remembered seeing something about pkg changes in the OmniOS r151038 release notes, and a quick search yielded this:

-m shows packages which were manually installed, that is explicitly provided as arguments to a pkg install command. Manually installed packages show an m in the flags column.

Now the error makes sense, even if the wording made it a little cryptic at first. Perhaps I was unfair.

So there’s a new flag in the IFO column, and Puppet doesn’t know how to deal with it. Options?

You can remove the ‘m’ flag with the pkg flag command,

# pkg list -H shell/zsh
shell/zsh                                         5.8-151038.0               im-
# pkg flag -M shell/zsh
Updating package state database                 Done
Updating image state                            Done
1 package updated.
# pkg list -H shell/zsh
shell/zsh                                         5.8-151038.0               i--

but I can’t see an elegant way to fix the problem with that. There are other new flag values too, and I think they all provide useful info, so muting them is off the agenda.

The only solution is to educate Puppet.

I understand why the author wrote the code so defensively. It’s better to say “I don’t know” than to wade in and do the wrong thing. (As true in life as in code.) But, I see no reason why Puppet should care about the any of the new flags, at least not with its current feature set.1

To have Puppet ignore the new flags, just like it always ignored the -, we only have to change that when statement to

when '-', 'm', 'M', 'r', 'R'

The fix was easy, but deploying the fix is harder. I don’t want to do something long-winded, like building and deploying a forked gem; or doing something awful like sedding the stock gem after it’s installed.

Ideally, of course, I’d like to get a proper fix back into the official distribution. But even if I managed to do that, I use Puppet 5, which is two major version numbers out of date now, and I have no idea at all how Puppet deal with backporting fixes. More importantly, I need if fixed now because I can’t build new zones.

My Puppet master already exports a fork of Oracle’s Solaris providers, with a few tweaks for Illumos, and my first thought was to copy a modified version of the provider into there, hoping that the master’s version of the provider would be preferred to the clients. So I did, and it wasn’t. Then I spent a good while trying to coax it into that behaviour with modulepath, again without success.

Eventually I went to the Puppet community Slack and a couple of very helpful people there explained that because the pkg provider is a “built in”, if Puppet finds other versions of it, they will always be ignored in favour of the bundled one. It was also suggested I subclass the pkg provider, and use my site manifest to tell the OmniOS clients to use it, and that’s exactly what I did.

This file is /opt/local/etc/puppetlabs/code/modules/solaris_providers/lib/puppet/provider/package/oopkg.rb on my master. I inherit from the existing pkg provider, and override that one troublesome method.

require 'puppet/provider/package'

Puppet::Type.type(:package).provide :oopkg, :parent => Puppet::Type.type(:package).provider(:pkg) do
  def self.ifo_flag(flags)
    (
      case flags[0..0]
      when 'i'
        {:status => 'installed'}
      when '-'
        {:status => 'known'}
      else
        raise ArgumentError, _('Unknown format %{resource_name}: %{full_flags}[%{bad_flag}]') %
            { resource_name: self.name, full_flags: flags, bad_flag: flags[0..0] }
      end
    ).merge(
      case flags[1..1]
      when 'f'
        {:ensure => 'held'}
      when '-', 'm', 'M', 'r', 'R'
        {}
      else
        raise ArgumentError, _('Unknown format %{resource_name}: %{full_flags}[%{bad_flag}]') %
            { resource_name: self.name, full_flags: flags, bad_flag: flags[1..1] }
      end
    )
  end
end

To use the new oopkg provider, I put this in my site.pp

if $facts['os']['name'] == 'OmniOS' {
  Package {
    provider => 'oopkg'
  }
}

I intend to send Puppet a proper patch to deal with the new OmniOS features, but I needed a “right now” fix, and I think the one I ended up with wasn’t too messy.

Enough. I have zones to build.

  1. I suppose we could have Puppet refuse to remove M flagged packages, which are implicitly fulfilled dependencies, but I don’t think even that would be a particularly useful feature. 

tags