Puppet Augeas Shells Provider

I’ve recently had the need to create a handful of small file based providers in puppet and while trundling uphill against the ParsedFile provider I decided to have a look at how custom providers are written using the Puppet augeas providers.

My sample provider is a simple one, it manages entries in /etc/shells and was quite quick to write. While augeasproviders assumes you know how Puppet types and providers are structured it does present a very standardised way of using the augeas library in order to manipulate your target. The three mainstay functions of a provider are presented in augeas form below -

  def exists?
    aug = nil
    path = "/files#{self.class.file(resource)}"

    begin
      aug = self.class.augopen(resource)
      ! aug.match("#{path}/*[.='#{resource[:name]}']").empty?
    ensure
      aug.close if aug
    end
  end

  def destroy
    aug = nil
    path = "/files#{self.class.file(resource)}"

    begin
      aug = self.class.augopen(resource)
       aug.rm("#{path}/*[.='#{resource[:name]}']")
      augsave!(aug)
    ensure
      aug.close if aug
    end
  end

  def create
    aug = nil
    path = "/files#{self.class.file(resource)}"

    begin
      aug = self.class.augopen(resource)
      aug.set("#{path}/01", resource[:name])
      augsave!(aug)
    ensure
      aug.close if aug
    end
  end

and although it might seem quite a lot of code for such a simple use case parts of it are abstractable boilerplate. Which I’d expect to reduced down as time goes on and the library matures. Once you’ve read through the code of an existing augeas based provider you can very quickly see where you’d need to add your own value lookups and modifications when writing a new provider -

# the file our provider operates on
path = "/files#{self.class.file(resource)}"

# does our value exist?
aug.match("#{path}/*[.='#{resource[:name]}']").empty?

# remove it on ensure => 'absent'
aug.rm("#{path}/*[.='#{resource[:name]}']")

# add it on ensure => 'present'
aug.set("#{path}/01", resource[:name])

I’ve never been too keen on using augeas as a first class puppet type; I think it enables you to easily start hacking parts of a file around rather than cleanly managing entire resources. Based on my experiments using it as the backend to a provider however I think it’s an excellent fit for certain resource patterns and something that should be in your puppet toolbox.

My ‘shells’ provider hasn’t been submitted upstream yet as I’ve not figured out how to write the required tests, (and this is one comprehensively tested set of providers!) but main coding experience was quick, quite painless and an approach I’ve be willing to use again in the future. Especially when the required boilerplate is reduced a little. Congratulations to all involved in the project.