{"id":8543,"date":"2025-05-30T13:37:31","date_gmt":"2025-05-30T11:37:31","guid":{"rendered":"https:\/\/www.credativ.de\/?p=8543"},"modified":"2025-11-24T16:58:00","modified_gmt":"2025-11-24T15:58:00","slug":"puppet-custom-types-and-providers","status":"publish","type":"post","link":"https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/","title":{"rendered":"Puppet Custom Types and Providers"},"content":{"rendered":"<h3>Introduction<\/h3>\n<p>Puppet is a software configuration management solution to manage IT infrastructure. One of the first things to be learnt about Puppet is its domain-specific language &#8211; the Puppet-DSL &#8211; and the concepts that come with it.<\/p>\n<p>Users can organize their code in classes and modules and use pre-defined resource types to manage resources like <a href=\"https:\/\/www.puppet.com\/docs\/puppet\/8\/types\/file.html\">files<\/a>, <a href=\"https:\/\/www.puppet.com\/docs\/puppet\/8\/types\/package.html\">packages<\/a>, <a href=\"https:\/\/www.puppet.com\/docs\/puppet\/8\/types\/services.htm\">services<\/a> and others.<\/p>\n<p>The most commonly used types are part of the Puppet core, implemented in Ruby. Composite resource types may be defined via Puppet-DSL from already known types by the Puppet user themself, or imported as part of an external Puppet module which is maintained by external module developers.<\/p>\n<p>It so happens that Puppet users can stay within the Puppet-DSL for a long time even when they deal with Puppet on a regular basis.<\/p>\n<p>The first time I had a glimpse into this topic was when Debian Stable was shipping Puppet 5.5, which was not too long ago. The Puppet 5.5 documentation includes a chapter on <a href=\"https:\/\/www.puppet.com\/docs\/puppet\/5.5\/custom_types.html\">custom types<\/a> and <a href=\"https:\/\/www.puppet.com\/docs\/puppet\/5.5\/provider_development\">provider development<\/a> respectively, but to me they felt incomplete and lacking self contained examples. Apparently <a href=\"http:\/\/garylarizza.com\/blog\/2013\/11\/25\/fun-with-providers\/\">I was not the only one feeling that way<\/a>, even though <a href=\"https:\/\/www.rubydoc.info\/gems\/puppet\/Puppet\/\">Puppet&#8217;s gem documentation<\/a> is a good overview of what is possible in principle.<\/p>\n<p>Gary Larizza&#8217;s blog post was more than ten years ago. I had another look into the <a href=\"https:\/\/www.puppet.com\/docs\/puppet\/7\/custom_resources\">documentation for Puppet 7 on that topic<\/a> recently, as this is the Puppet version in current&#8217;s Debian Stable.<\/p>\n<p>The Puppet 5.5 way to type &amp; provider development is now called the <a href=\"https:\/\/www.puppet.com\/docs\/puppet\/7\/types_and_providers_method\">low level method<\/a>, and its documentation has not changed significantly. However, Puppet 6 upwards recommends a <a href=\"https:\/\/www.puppet.com\/docs\/puppet\/7\/create_types_and_providers_resource_api\">new method to create custom types &amp; providers<\/a> via the so-called <a href=\"https:\/\/github.com\/puppetlabs\/puppet-resource_api\">Resource-API<\/a>, whose <a href=\"https:\/\/github.com\/puppetlabs\/puppet-specifications\/blob\/master\/language\/resource-api\/README.md\">documentation<\/a> is a major improvement compared to the low-level method&#8217;s. The Resource-API is not a replacement, though, and has several <a href=\"https:\/\/github.com\/puppetlabs\/puppet-specifications\/blob\/master\/language\/resource-api\/README.md#known-limitations\">documented limitations<\/a>.<\/p>\n<p>Nevertheless, for the remaining blog post, we will re-prototype a small portion of <code>file<\/code>s functionality using the low-level method, as well as the Resource-API, namely the <code>ensure<\/code> and <code>content<\/code> properties.<\/p>\n<h3>Preparations<\/h3>\n<p>The following preparations are not necessary in an agent-server setup. We use <code>bundle<\/code> to obtain a <code>puppet<\/code> executable for this demo.<\/p>\n<pre>demo@85c63b50bfa3:~$ cat &gt; Gemfile &lt;&lt;EOF\r\nsource 'https:\/\/rubygems.org'\r\n\r\ngem 'puppet', '&gt;= 6'\r\nEOF\r\n<\/pre>\n<pre>demo@85c63b50bfa3:~$ bundle install\r\nFetching gem metadata from https:\/\/rubygems.org\/........\r\nResolving dependencies...\r\n...\r\nInstalling puppet 8.10.0\r\nBundle complete! 1 Gemfile dependency, 17 gems now installed.\r\nBundled gems are installed into `.\/.vendor`\r\n<\/pre>\n<pre>demo@85c63b50bfa3:~$ cat &gt; file_builtin.pp &lt;&lt;EOF\r\n$file = '\/home\/demo\/puppet-file-builtin'\r\nfile {$file: content =&gt; 'This is madness'}\r\nEOF\r\n<\/pre>\n<pre>demo@85c63b50bfa3:~$ bin\/puppet apply file_builtin.pp\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.01 seconds\r\nNotice: \/Stage[main]\/Main\/File[\/home\/demo\/puppet-file-builtin]\/ensure: defined content as '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e'\r\nNotice: Applied catalog in 0.04 seconds\r\n\r\ndemo@85c63b50bfa3:~$ sha256sum \/home\/demo\/puppet-file-builtin\r\n0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e \/home\/demo\/puppet-file-builtin\r\n<\/pre>\n<p>Keep in mind the state change information printed by Puppet.<\/p>\n<h3>Low-level prototype<\/h3>\n<p>Custom types and providers that are not installed via a Gem need to be part of some Puppet module, so they can be copied to Puppet agents via the <a href=\"https:\/\/www.puppet.com\/docs\/puppet\/8\/subsystem_agent_primary_comm\">pluginsync mechanism<\/a>.<\/p>\n<p>A common location for Puppet modules is the <code>modules<\/code> directory inside a <a href=\"https:\/\/www.puppet.com\/docs\/puppet\/8\/environments_creating#environment-modulepath\">Puppet environment<\/a>. For this demo, we declare a <code>demo<\/code> module.<\/p>\n<h4>Basic functionality<\/h4>\n<p>Our first attempt is the following type definition for a new type we will call <code>file_llmethod<\/code>. It has no documentation or validation of input values.<\/p>\n<pre># modules\/demo\/lib\/puppet\/type\/file_llmethod.rb\r\n\r\nPuppet::Type.newtype(:file_llmethod) do\r\n  newparam(:path, namevar: true) {}\r\n  newproperty(:content) {}\r\n  newproperty(:ensure) do\r\n    newvalues(:present, :absent)\r\n    defaultto(:present)\r\n  end\r\nend\r\n<\/pre>\n<p>We have declared a <code>path<\/code> parameter that serves as the <code>namevar<\/code> for this type &#8211; there cannot be other <code>file_llmethod<\/code> instances managing the same <code>path<\/code>. The <code>ensure<\/code> property is restricted to two values and defaults to <code>present<\/code>.<\/p>\n<p>The following provider implementation consists of a getter and a setter for each of the two properties <code>content<\/code> and <code>ensure<\/code>.<\/p>\n<pre># modules\/demo\/lib\/puppet\/provider\/file_llmethod\/ruby.rb\r\n\r\nPuppet::Type.type(:file_llmethod).provide(:ruby) do\r\n  def ensure\r\n    File.exist?(@resource[:path]) ? :present : :absent\r\n  end\r\n\r\n  def ensure=(value)\r\n    if value == :present\r\n      # reuse setter\r\n      self.content=(@resource[:content])\r\n    else\r\n      File.unlink(@resource[:path])\r\n    end\r\n  end\r\n\r\n  def content\r\n    File.read(@resource[:path])\r\n  end\r\n\r\n  def content=(value)\r\n    File.write(@resource[:path], value)\r\n  end\r\nend\r\n<\/pre>\n<p>This gives us the following:<\/p>\n<pre>demo@85c63b50bfa3:~$ cat &gt; file_llmethod_create.pp &lt;&lt;EOF\r\n$file = '\/home\/demo\/puppet-file-lowlevel-method-create'\r\nfile {$file: ensure =&gt; absent} -&gt;\r\nfile_llmethod {$file: content =&gt; 'This is Sparta!'}\r\nEOF\r\n\r\ndemo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules file_llmethod_create.pp\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.01 seconds\r\nNotice: \/Stage[main]\/Main\/File_llmethod[\/home\/demo\/puppet-file-lowlevel-method-create]\/ensure: defined 'ensure' as 'present'\r\n\r\ndemo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules file_llmethod_create.pp\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.01 seconds\r\nNotice: \/Stage[main]\/Main\/File[\/home\/demo\/puppet-file-lowlevel-method-create]\/ensure: removed\r\nNotice: \/Stage[main]\/Main\/File_llmethod[\/home\/demo\/puppet-file-lowlevel-method-create]\/ensure: defined 'ensure' as 'present'\r\n\r\ndemo@85c63b50bfa3:~$ cat &gt; file_llmethod_change.pp &lt;&lt;EOF\r\n$file = '\/home\/demo\/puppet-file-lowlevel-method-change'\r\nfile {$file: content =&gt; 'This is madness'} -&gt;\r\nfile_llmethod {$file: content =&gt; 'This is Sparta!'}\r\nEOF\r\n\r\ndemo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules file_llmethod_change.pp\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds\r\nNotice: \/Stage[main]\/Main\/File[\/home\/demo\/puppet-file-lowlevel-method-change]\/ensure: defined content as '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e'\r\nNotice: \/Stage[main]\/Main\/File_llmethod[\/home\/demo\/puppet-file-lowlevel-method-change]\/content: content changed 'This is madness' to 'This is Sparta!'\r\n\r\ndemo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules file_llmethod_change.pp\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds\r\nNotice: \/Stage[main]\/Main\/File[\/home\/demo\/puppet-file-lowlevel-method-change]\/content: content changed '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2' to '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e'\r\nNotice: \/Stage[main]\/Main\/File_llmethod[\/home\/demo\/puppet-file-lowlevel-method-change]\/content: content changed 'This is madness' to 'This is Sparta!'\r\n<\/pre>\n<p>Our custom type already kind of works, even though we have not implemented any explicit comparison of is- and should-state. Puppet does this for us based on the Puppet catalog and the property getter return values. Our defined setters are also invoked by Puppet on demand, only.<\/p>\n<p>We can also see that the <code>ensure<\/code> state change notice is <code>defined 'ensure' as 'present'<\/code> and does not incorporate the desired <code>content<\/code> in any way, while the <code>content<\/code> state change notice shows plain text. Both tell us that the SHA256 checksum from the <code>file_builtin.pp<\/code> example is already something non-trivial.<\/p>\n<h4>Validating input<\/h4>\n<p>As a next step we add <a href=\"https:\/\/www.rubydoc.info\/gems\/puppet\/Puppet\/Parameter#validate-class_method\">validation<\/a> for <code>path<\/code> and <code>content<\/code>.<\/p>\n<pre># modules\/demo\/lib\/puppet\/type\/file_llmethod.rb\r\n\r\nPuppet::Type.newtype(:file_llmethod) do\r\n  newparam(:path, namevar: true) do\r\n    validate do |value|\r\n      fail \"#{value} is not a String\" unless value.is_a?(String)\r\n      fail \"#{value} is not an absolute path\" unless File.absolute_path?(value)\r\n    end\r\n  end\r\n\r\n  newproperty(:content) do\r\n    validate do |value|\r\n      fail \"#{value} is not a String\" unless value.is_a?(String)\r\n    end\r\n  end\r\n\r\n  newproperty(:ensure) do\r\n    newvalues(:present, :absent)\r\n    defaultto(:present)\r\n  end\r\nend\r\n<\/pre>\n<p>Failed validations will look like these:<\/p>\n<pre>demo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules --exec 'file_llmethod {\".\/relative\/path\": }'\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds\r\nError: Parameter path failed on File_llmethod[.\/relative\/path]: .\/relative\/path is not an absolute path (line: 1)\r\n\r\ndemo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules --exec 'file_llmethod {\"\/absolute\/path\": content =&gt; 42}'\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds\r\nError: Parameter content failed on File_llmethod[\/absolute\/path]: 42 is not a String (line: 1)\r\n<\/pre>\n<h4>Content Checksums<\/h4>\n<p>We override <a href=\"https:\/\/www.rubydoc.info\/gems\/puppet\/Puppet\/Property#change_to_s-instance_method\">change_to_s<\/a> so that state changes include content checksums:<\/p>\n<pre># modules\/demo\/lib\/puppet\/type\/file_llmethod.rb\r\n\r\nrequire 'digest'\r\n\r\nPuppet::Type.newtype(:file_llmethod) do\r\n  newparam(:path, namevar: true) do\r\n    validate do |value|\r\n      fail \"#{value} is not a String\" unless value.is_a?(String)\r\n      fail \"#{value} is not an absolute path\" unless File.absolute_path?(value)\r\n    end\r\n  end\r\n\r\n  newproperty(:content) do\r\n    validate do |value|\r\n      fail \"#{value} is not a String\" unless value.is_a?(String)\r\n    end\r\n    define_method(:change_to_s) do |currentvalue, newvalue|\r\n      old = \"{sha256}#{Digest::SHA256.hexdigest(currentvalue)}\"\r\n      new = \"{sha256}#{Digest::SHA256.hexdigest(newvalue)}\"\r\n      \"content changed '#{old}' to '#{new}'\"\r\n    end\r\n  end\r\n\r\n  newproperty(:ensure) do\r\n    define_method(<code>:change_to_s<\/code>) do |currentvalue, newvalue| if currentvalue == :absent should = @resource.property(:content).should digest = \"{sha256}#{Digest::SHA256.hexdigest(should)}\" \"defined content as '#{digest}'\" else super(currentvalue, newvalue) end end newvalues(:present, :absent) defaultto(:present) end end<\/pre>\n<p>The above type definition yields:<\/p>\n<pre>demo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules file_llmethod_create.pp\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds\r\nNotice: \/Stage[main]\/Main\/File[\/home\/demo\/puppet-file-lowlevel-method-create]\/ensure: removed\r\nNotice: \/Stage[main]\/Main\/File_llmethod[\/home\/demo\/puppet-file-lowlevel-method-create]\/ensure: defined content as '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2'\r\n\r\ndemo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules file_llmethod_change.pp\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds\r\nNotice: \/Stage[main]\/Main\/File[\/home\/demo\/puppet-file-lowlevel-method-change]\/content: content changed '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2' to '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e'\r\nNotice: \/Stage[main]\/Main\/File_llmethod[\/home\/demo\/puppet-file-lowlevel-method-change]\/content: content changed '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e' to '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2'\r\n<\/pre>\n<h4>Improving memory footprint<\/h4>\n<p>So far so good. While our current implementation apparently works, it has at least one major flaw. If the managed file already exists, the provider stores the file&#8217;s whole content in memory.<\/p>\n<pre>demo@85c63b50bfa3:~$ cat &gt; file_llmethod_change_big.pp &lt;&lt;EOF\r\n$file = '\/home\/demo\/puppet-file-lowlevel-method-change_big'\r\nfile_llmethod {$file: content =&gt; 'This is Sparta!'}\r\nEOF\r\n\r\ndemo@85c63b50bfa3:~$ rm -f \/home\/demo\/puppet-file-lowlevel-method-change_big\r\ndemo@85c63b50bfa3:~$ ulimit -Sv 200000\r\n\r\ndemo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules file_llmethod_change_big.pp\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds\r\nNotice: \/Stage[main]\/Main\/File_llmethod[\/home\/demo\/puppet-file-lowlevel-method-change_big]\/ensure: defined content as '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2'\r\nNotice: Applied catalog in 0.02 seconds\r\n\r\ndemo@85c63b50bfa3:~$ dd if=\/dev\/zero of=\/home\/demo\/puppet-file-lowlevel-method-change_big seek=8G bs=1 count=1\r\n1+0 records in\r\n1+0 records out\r\n1 byte copied, 8.3047e-05 s, 12.0 kB\/s\r\n\r\ndemo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules file_llmethod_change_big.pp\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds\r\nError: Could not run: failed to allocate memory\r\n<\/pre>\n<p>Instead, the implementation should only store the checksum so that Puppet can decide based on checksums if our <code>content=<\/code> setter needs to be invoked.<\/p>\n<p>This also means that the Puppet catalog&#8217;s <code>content<\/code> needs to be checksummed by <a href=\"https:\/\/www.rubydoc.info\/gems\/puppet\/Puppet\/Parameter#munge-class_method\">munge<\/a> before it it processed by Puppet&#8217;s internal comparison routine. Luckily we also have access to the original value via <a href=\"https:\/\/www.rubydoc.info\/gems\/puppet\/Puppet%2FProperty:shouldorig\">shouldorig<\/a>.<\/p>\n<pre># modules\/demo\/lib\/puppet\/type\/file_llmethod.rb\r\n\r\nrequire 'digest'\r\n\r\nPuppet::Type.newtype(:file_llmethod) do\r\n  newparam(:path, namevar: true) do\r\n    validate do |value|\r\n      fail \"#{value} is not a String\" unless value.is_a?(String)\r\n      fail \"#{value} is not an absolute path\" unless File.absolute_path?(value)\r\n    end\r\n  end\r\n\r\n  newproperty(:content) do\r\n    validate do |value|\r\n      fail \"#{value} is not a String\" unless value.is_a?(String)\r\n    end\r\n    munge do |value|\r\n      \"{sha256}#{Digest::SHA256.hexdigest(value)}\"\r\n    end\r\n    # No need to override change_to_s with munging\r\n  end\r\n\r\n  newproperty(:ensure) do\r\n    define_method(:change_to_s) do |currentvalue, newvalue|\r\n      if currentvalue == :absent\r\n        should = @resource.property(:content).should\r\n        \"defined content as '#{should}'\"\r\n      else\r\n        super(currentvalue, newvalue)\r\n      end\r\n    end\r\n    newvalues(:present, :absent)\r\n    defaultto(:present)\r\n  end\r\nend\r\n<\/pre>\n<pre># modules\/demo\/lib\/puppet\/provider\/file_llmethod\/ruby.rb\r\n\r\nPuppet::Type.type(:file_llmethod).provide(:ruby) do\r\n  ...\r\n\r\n  def content\r\n    File.open(@resource[:path], 'r') do |file|\r\n      sha = Digest::SHA256.new\r\n      while chunk = file.read(2**16)\r\n        sha &lt;&lt; chunk\r\n      end\r\n      \"{sha256}#{sha.hexdigest}\"\r\n    end\r\n  end\r\n\r\n  def content=(value)\r\n    # value is munged, but we need to write the original\r\n    File.write(@resource[:path], @resource.parameter(:content).shouldorig[0])\r\n  end\r\nend\r\n<\/pre>\n<p>Now we can manage big files:<\/p>\n<pre>demo@85c63b50bfa3:~$ ulimit -Sv 200000\r\ndemo@85c63b50bfa3:~$ dd if=\/dev\/zero of=\/home\/demo\/puppet-file-lowlevel-method-change_big seek=8G bs=1 count=1\r\n1+0 records in\r\n1+0 records out\r\n1 byte copied, 9.596e-05 s, 10.4 kB\/s\r\ndemo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules file_llmethod_change_big.pp\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds\r\nNotice: \/Stage[main]\/Main\/File_llmethod[\/home\/demo\/puppet-file-lowlevel-method-change_big]\/content: content changed '{sha256}ef17a425c57a0e21d14bec2001d8fa762767b97145b9fe47c5d4f2fda323697b' to '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2'\r\n<\/pre>\n<h4>Ensure it the Puppet way<\/h4>\n<p>There is still something not right. Maybe you have noticed that our provider&#8217;s <code>content<\/code> getter attempts to open a file unconditionally, and yet the <code>file_llmethod_create.pp<\/code> run has not produced an error. It seems that an <code>ensure<\/code> transition from <code>absent<\/code> to <code>present<\/code> short-circuits the <code>content<\/code> getter, even though we have not expressed a wish to do so.<\/p>\n<p>It turns out that an <code>ensure<\/code> property gets special treatment by Puppet. If we had attempted to use a <code>makeitso<\/code> property instead of <code>ensure<\/code>, there would be no short-circuiting and the <code>content<\/code> getter would raise an exception.<\/p>\n<p>We will not fix the <code>content<\/code> getter though. If Puppet has special treatment for <code>ensure<\/code>, we should use Puppet&#8217;s intended mechanism for it, and declare the type <a href=\"https:\/\/www.rubydoc.info\/gems\/puppet\/Puppet\/Type#ensurable-class_method\">ensurable<\/a>:<\/p>\n<pre># modules\/demo\/lib\/puppet\/type\/file_llmethod.rb\r\n\r\nrequire 'digest'\r\n\r\nPuppet::Type.newtype(:file_llmethod) do\r\n  ensurable\r\n\r\n  newparam(:path, namevar: true) do\r\n    validate do |value|\r\n      fail \"#{value} is not a String\" unless value.is_a?(String)\r\n      fail \"#{value} is not an absolute path\" unless File.absolute_path?(value)\r\n    end\r\n  end\r\n\r\n  newproperty(:content) do\r\n    validate do |value|\r\n      fail \"#{value} is not a String\" unless value.is_a?(String)\r\n    end\r\n    munge do |value|\r\n      \"{sha256}#{Digest::SHA256.hexdigest(value)}\"\r\n    end\r\n  end\r\nend\r\n<\/pre>\n<p>With <code>ensurable<\/code> the provider needs to implement three new methods, but we can drop the <code>ensure<\/code> accessors:<\/p>\n<pre># modules\/demo\/lib\/puppet\/provider\/file_llmethod\/ruby.rb\r\n\r\nPuppet::Type.type(:file_llmethod).provide(:ruby) do\r\n  def exists?\r\n    File.exist?(@resource[:name])\r\n  end\r\n\r\n  def create\r\n    self.content=(:dummy)\r\n  end\r\n\r\n  def destroy\r\n    File.unlink(@resource[:name])\r\n  end\r\n\r\n  def content\r\n    File.open(@resource[:path], 'r') do |file|\r\n      sha = Digest::SHA256.new\r\n      while chunk = file.read(2**16)\r\n        sha &lt;&lt; chunk\r\n      end\r\n      \"{sha256}#{sha.hexdigest}\"\r\n    end\r\n  end\r\n\r\n  def content=(value)\r\n    # value is munged, but we need to write the original\r\n    File.write(@resource[:path], @resource.parameter(:content).shouldorig[0])\r\n  end\r\nend\r\n<\/pre>\n<p>However, now we have lost the SHA256 checksum on file creation:<\/p>\n<pre>demo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules file_llmethod_create.pp Notice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds Notice: \/Stage[main]\/Main\/File[\/home\/demo\/puppet-file-lowlevel-method-create]\/ensure: removed Notice: \/Stage[main]\/Main\/File_llmethod[\/home\/demo\/puppet-file-lowlevel-method-create]\/ensure: created\r\n<\/pre>\n<p>To get it back, we replace <code>ensurable<\/code> by an adapted implementation of it, which includes our previous <code>change_to_s<\/code> override:<\/p>\n<pre>newproperty(:ensure, :parent =&gt; Puppet::Property::Ensure) do\r\n  defaultvalues\r\n  define_method(:change_to_s) do |currentvalue, newvalue|\r\n    if currentvalue == :absent\r\n      should = @resource.property(:content).should\r\n      \"defined content as '#{should}'\"\r\n    else\r\n      super(currentvalue, newvalue)\r\n    end\r\n  end\r\nend\r\n<\/pre>\n<pre>demo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules file_llmethod_create.pp\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.02 seconds\r\nNotice: \/Stage[main]\/Main\/File[\/home\/demo\/puppet-file-lowlevel-method-create]\/ensure: removed\r\nNotice: \/Stage[main]\/Main\/File_llmethod[\/home\/demo\/puppet-file-lowlevel-method-create]\/ensure: defined content as '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2'\r\n<\/pre>\n<p>Our final low-level prototype is thus as follows.<\/p>\n<h4>Final low-level prototype<\/h4>\n<pre># modules\/demo\/lib\/puppet\/type\/file_llmethod.rb\r\n\r\n# frozen_string_literal: true\r\n\r\nrequire 'digest'\r\n\r\nPuppet::Type.newtype(:file_llmethod) do\r\n  newparam(:path, namevar: true) do\r\n    validate do |value|\r\n      fail \"#{value} is not a String\" unless value.is_a?(String)\r\n      fail \"#{value} is not an absolute path\" unless File.absolute_path?(value)\r\n    end\r\n  end\r\n\r\n  newproperty(:content) do\r\n    validate do |value|\r\n      fail \"#{value} is not a String\" unless value.is_a?(String)\r\n    end\r\n    munge do |value|\r\n      \"{sha256}#{Digest::SHA256.hexdigest(value)}\"\r\n    end\r\n  end\r\n\r\n  newproperty(:ensure, :parent =&gt; Puppet::Property::Ensure) do\r\n    defaultvalues\r\n    define_method(:change_to_s) do |currentvalue, newvalue|\r\n      if currentvalue == :absent\r\n        should = @resource.property(:content).should\r\n        \"defined content as '#{should}'\"\r\n      else\r\n        super(currentvalue, newvalue)\r\n      end\r\n    end\r\n  end\r\nend\r\n<\/pre>\n<pre># modules\/demo\/lib\/puppet\/provider\/file_llmethod\/ruby.rb\r\n\r\n# frozen_string_literal: true\r\n\r\nPuppet::Type.type(:file_llmethod).provide(:ruby) do\r\n  def exists?\r\n    File.exist?(@resource[:name])\r\n  end\r\n\r\n  def create\r\n    self.content=(:dummy)\r\n  end\r\n\r\n  def destroy\r\n    File.unlink(@resource[:name])\r\n  end\r\n\r\n  def content\r\n    File.open(@resource[:path], 'r') do |file|\r\n      sha = Digest::SHA256.new\r\n      while (chunk = file.read(2**16))\r\n        sha &lt;&lt; chunk\r\n      end\r\n      \"{sha256}#{sha.hexdigest}\"\r\n    end\r\n  end\r\n\r\n  def content=(_value)\r\n    # value is munged, but we need to write the original\r\n    File.write(@resource[:path], @resource.parameter(:content).shouldorig[0])\r\n  end\r\nend\r\n<\/pre>\n<h3>Resource-API prototype<\/h3>\n<p>According to the <a href=\"https:\/\/github.com\/puppetlabs\/puppet-specifications\/blob\/master\/language\/resource-api\/README.md\">Resource-API documentation<\/a> we need to define our new <code>file_rsapi<\/code> type by calling <code>Puppet::ResourceApi.register_type<\/code> with several parameters, amongst which are the desired attributes, even <code>ensure<\/code>.<\/p>\n<pre># modules\/demo\/lib\/puppet\/type\/file_rsapi.rb\r\n\r\nrequire 'puppet\/resource_api'\r\n\r\nPuppet::ResourceApi.register_type(\r\n  name: 'file_rsapi', \r\n  attributes: {\r\n    content: {\r\n      desc: 'description of content parameter',\r\n      type: 'String' \r\n    },\r\n    ensure: {\r\n      default: 'present',\r\n      desc: 'description of ensure parameter',\r\n      type: 'Enum[present, absent]'\r\n    },\r\n    path: {\r\n      behaviour: :namevar,\r\n      desc: 'description of path parameter',\r\n      type: 'Pattern[\/\\A\\\/([^\\n\\\/\\0]+\\\/*)*\\z\/]'\r\n    },\r\n  },\r\n  desc: 'description of file_rsapi'\r\n)\r\n<\/pre>\n<p>The <code>path<\/code> <code>type<\/code> uses a built-in Puppet data type. <code>Stdlib::Absolutepath<\/code> would have been more convenient but external data types are not possible with the Resource-API yet.<\/p>\n<p>In comparison with our low-level prototype, the above type definition has no SHA256-munging and SHA256-output counterparts. The <code>canonicalize<\/code> provider feature looks similar to munging, but we skip it for now.<\/p>\n<p>The <a href=\"https:\/\/github.com\/puppetlabs\/puppet-specifications\/blob\/master\/language\/resource-api\/README.md#resource-implementation-provider\">Resource-API documentation<\/a> tells us to implement a <code>get<\/code> and a <code>set<\/code> method in our provider, stating<\/p>\n<blockquote style=\"background-color: #e9ecef;\"><p>The get method reports the current state of the managed resources. <b>It returns an enumerable of all existing resources<\/b>. Each resource is a hash with attribute names as keys, and their respective values as values.<\/p><\/blockquote>\n<p>This demand is the first bummer, as we definitely do not want to read all files with their content and store it in memory. We can ignore this demand &#8211; how would the Resource-API know anyway.<\/p>\n<p>However, the documented signature is <code>def get(context) {...}<\/code> where <code>context<\/code> has no information about the resource we want to manage.<\/p>\n<p>This would have been a show-stopper, if the <a href=\"https:\/\/github.com\/puppetlabs\/puppet-specifications\/blob\/master\/language\/resource-api\/README.md#provider-feature-simple_get_filter\">simple_get_filter<\/a> provider feature didn&#8217;t exist, which changes the signature to <code>def get(context, names = nil) {...}<\/code>.<\/p>\n<p>Our first version of <code>file_rsapi<\/code> is thus the following.<\/p>\n<h4>Basic functionality<\/h4>\n<pre># modules\/demo\/lib\/puppet\/type\/file_rsapi.rb\r\n\r\nrequire 'puppet\/resource_api'\r\n\r\nPuppet::ResourceApi.register_type(\r\n  name: 'file_rsapi',\r\n  features: %w[simple_get_filter],\r\n  attributes: {\r\n    content: {\r\n      desc: 'description of content parameter',\r\n      type: 'String' \r\n    },\r\n    ensure: {\r\n      default: 'present',\r\n      desc: 'description of ensure parameter',\r\n      type: 'Enum[present, absent]'\r\n    },\r\n    path: {\r\n      behaviour: :namevar,\r\n      desc: 'description of path parameter',\r\n      type: 'Pattern[\/\\A\\\/([^\\n\\\/\\0]+\\\/*)*\\z\/]'\r\n    },\r\n  },\r\n  desc: 'description of file_rsapi'\r\n)\r\n<\/pre>\n<pre># modules\/demo\/lib\/puppet\/provider\/file_rsapi\/file_rsapi.rb\r\n\r\nrequire 'digest'\r\n\r\nclass Puppet::Provider::FileRsapi::FileRsapi\r\n  def get(context, names)\r\n    (names or []).map do |name|\r\n      File.exist?(name) ? {\r\n        path: name,\r\n        ensure: 'present',\r\n        content: filedigest(name),\r\n      } : nil\r\n    end.compact # remove non-existing resources\r\n  end\r\n\r\n  def set(context, changes)\r\n    changes.each do |path, change|\r\n      if change[:should][:ensure] == 'present'\r\n        File.write(path, change[:should][:content])\r\n      elsif File.exist?(path)\r\n        File.delete(path)\r\n      end\r\n    end\r\n  end\r\n\r\n  def filedigest(path)\r\n    File.open(path, 'r') do |file|\r\n      sha = Digest::SHA256.new\r\n      while chunk = file.read(2**16)\r\n         sha &lt;&lt; chunk\r\n      end\r\n      \"{sha256}#{sha.hexdigest}\"\r\n    end\r\n  end\r\nend\r\n<\/pre>\n<p>The desired <code>content<\/code> is written correctly into the file, but we have again no SHA256 checksum on creation as well as unnecessary writes, because the checksum from <code>get<\/code> does not match the cleartext from the catalog:<\/p>\n<pre>demo@85c63b50bfa3:~$ cat &gt; file_rsapi_create.pp &lt;&lt;EOF\r\n$file = '\/home\/demo\/puppet-file-rsapi-create'\r\nfile       {$file: ensure  =&gt; absent} -&gt;\r\nfile_rsapi {$file: content =&gt; 'This is Sparta!'}\r\nEOF\r\n\r\ndemo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules file_rsapi_create.pp\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.03 seconds\r\nNotice: \/Stage[main]\/Main\/File[\/home\/demo\/puppet-file-rsapi-create]\/ensure: removed\r\nNotice: \/Stage[main]\/Main\/File_rsapi[\/home\/demo\/puppet-file-rsapi-create]\/ensure: defined 'ensure' as 'present'\r\nNotice: Applied catalog in 0.02 seconds\r\n\r\ndemo@85c63b50bfa3:~$ cat &gt; file_rsapi_change.pp &lt;&lt;EOF\r\n$file = '\/home\/demo\/puppet-file-rsapi-change'\r\nfile       {$file: content =&gt; 'This is madness'} -&gt;\r\nfile_rsapi {$file: content =&gt; 'This is Sparta!'}\r\nEOF\r\n\r\ndemo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules file_rsapi_change.pp\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.03 seconds\r\nNotice: \/Stage[main]\/Main\/File[\/home\/demo\/puppet-file-rsapi-change]\/content: content changed '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2' to '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e'\r\nNotice: \/Stage[main]\/Main\/File_rsapi[\/home\/demo\/puppet-file-rsapi-change]\/content: content changed '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e' to 'This is Sparta!'\r\nNotice: Applied catalog in 0.03 seconds\r\n<\/pre>\n<p>Hence we enable and implement the <code>canonicalize<\/code> provider feature.<\/p>\n<h4>Canonicalize<\/h4>\n<p>According to the documentation, <code>canonicalize<\/code> is applied to the results of <code>get<\/code> as well as to the catalog properties. On the one hand, we do not want to read the file&#8217;s content into memory, on the other hand cannot checksum the file&#8217;s content twice.<\/p>\n<p>An easy way would be to check whether the <code>canonicalized<\/code> call happens after the <code>get<\/code> call:<\/p>\n<pre>def canonicalize(context, resources)\r\n  if @stage == :get\r\n    # do nothing, is-state canonicalization already performed by get()\r\n  else\r\n    # catalog canonicalization\r\n    ...\r\n  end\r\nend\r\n\r\ndef get(context, paths)\r\n  @stage = :get\r\n\r\n  ...\r\nend\r\n<\/pre>\n<p>While this works for the current implementation of the Resource-API, there is no guarantee about the order of <code>canonicalize<\/code> calls. Instead we subclass from <code>String<\/code> and handle checksumming internally. We also add some state change calls to the appropriate <code>context<\/code> methods.<\/p>\n<p>Our final Resource-API-based prototype implementation is:<\/p>\n<pre># modules\/demo\/lib\/puppet\/type\/file_rsapi.rb\r\n\r\n# frozen_string_literal: true\r\n\r\nrequire 'puppet\/resource_api'\r\n\r\nPuppet::ResourceApi.register_type(\r\n  name: 'file_rsapi',\r\n  features: %w[simple_get_filter canonicalize],\r\n  attributes: {\r\n    content: {\r\n      desc: 'description of content parameter',\r\n      type: 'String'\r\n    },\r\n    ensure: {\r\n      default: 'present',\r\n      desc: 'description of ensure parameter',\r\n      type: 'Enum[present, absent]'\r\n    },\r\n    path: {\r\n      behaviour: :namevar,\r\n      desc: 'description of path parameter',\r\n      type: 'Pattern[\/\\A\\\/([^\\n\\\/\\0]+\\\/*)*\\z\/]'\r\n    }\r\n  },\r\n  desc: 'description of file_rsapi'\r\n)\r\n<\/pre>\n<pre># modules\/demo\/lib\/puppet\/provider\/file_rsapi\/file_rsapi.rb\r\n\r\n# frozen_string_literal: true\r\n\r\nrequire 'digest'\r\nrequire 'pathname'\r\n\r\nclass Puppet::Provider::FileRsapi::FileRsapi\r\n  class CanonicalString &lt; String\r\n    attr_reader :original\r\n\r\n    def class\r\n      # Mask as String for YAML.dump to mitigate\r\n      #   Error: Transaction store file \/var\/cache\/puppet\/state\/transactionstore.yaml\r\n      #   is corrupt ((\/var\/cache\/puppet\/state\/transactionstore.yaml): Tried to\r\n      #   load unspecified class: Puppet::Provider::FileRsapi::FileRsapi::CanonicalString)\r\n      String\r\n    end\r\n\r\n    def self.from(obj)\r\n      return obj if obj.is_a?(self)\r\n      return new(filedigest(obj)) if obj.is_a?(Pathname)\r\n\r\n      new(\"{sha256}#{Digest::SHA256.hexdigest(obj)}\", obj)\r\n    end\r\n\r\n    def self.filedigest(path)\r\n      File.open(path, 'r') do |file|\r\n        sha = Digest::SHA256.new\r\n        while (chunk = file.read(2**16))\r\n          sha &lt;&lt; chunk\r\n        end\r\n        \"{sha256}#{sha.hexdigest}\"\r\n      end\r\n    end\r\n\r\n    def initialize(canonical, original = nil)\r\n      @original = original\r\n      super(canonical)\r\n    end\r\n  end\r\n\r\n  def canonicalize(_context, resources)\r\n    resources.each do |resource|\r\n      next if resource[:ensure] == 'absent'\r\n\r\n      resource[:content] = CanonicalString.from(resource[:content])\r\n    end\r\n    resources\r\n  end\r\n\r\n  def get(_context, names)\r\n    (names or []).map do |name|\r\n      next unless File.exist?(name)\r\n\r\n      {\r\n        content: CanonicalString.from(Pathname.new(name)),\r\n        ensure: 'present',\r\n        path: name\r\n      }\r\n    end.compact # remove non-existing resources\r\n  end\r\n\r\n  def set(context, changes)\r\n    changes.each do |path, change|\r\n      if change[:should][:ensure] == 'present'\r\n        File.write(path, change[:should][:content].original)\r\n        if change[:is][:ensure] == 'present'\r\n          # The only other possible change is due to content,\r\n          # but content change transition info is covered implicitly\r\n        else\r\n          context.created(\"#{path} with content '#{change[:should][:content]}'\")\r\n        end\r\n      elsif File.exist?(path)\r\n        File.delete(path)\r\n        context.deleted(path)\r\n      end\r\n    end\r\n  end\r\nend\r\n<\/pre>\n<p>It gives us:<\/p>\n<pre>demo@85c63b50bfa3:~$ cat &gt; file_rsapi_create.pp &lt;&lt;EOF\r\n$file = '\/home\/demo\/puppet-file-rsapi-create'\r\nfile       {$file: ensure  =&gt; absent} -&gt;\r\nfile_rsapi {$file: content =&gt; 'This is Sparta!'}\r\nEOF\r\n\r\ndemo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules file_rsapi_create.pp\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.03 seconds\r\nNotice: \/Stage[main]\/Main\/File[\/home\/demo\/puppet-file-rsapi-create]\/ensure: removed\r\nNotice: \/Stage[main]\/Main\/File_rsapi[\/home\/demo\/puppet-file-rsapi-create]\/ensure: defined 'ensure' as 'present'\r\nNotice: file_rsapi: Created: \/home\/demo\/puppet-file-rsapi-create with content '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2'\r\n\r\ndemo@85c63b50bfa3:~$ cat &gt; file_rsapi_change.pp &lt;&lt;EOF\r\n$file = '\/home\/demo\/puppet-file-rsapi-change'\r\nfile       {$file: content =&gt; 'This is madness'} -&gt;\r\nfile_rsapi {$file: content =&gt; 'This is Sparta!'}\r\nEOF\r\n\r\ndemo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules file_rsapi_change.pp\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.03 seconds\r\nNotice: \/Stage[main]\/Main\/File[\/home\/demo\/puppet-file-rsapi-change]\/content: content changed '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2' to '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e'\r\nNotice: \/Stage[main]\/Main\/File_rsapi[\/home\/demo\/puppet-file-rsapi-change]\/content: content changed '{sha256}0549defd0a7d6d840e3a69b82566505924cacbe2a79392970ec28cddc763949e' to '{sha256}823cbb079548be98b892725b133df610d0bff46b33e38b72d269306d32b73df2'\r\n\r\ndemo@85c63b50bfa3:~$ cat &gt; file_rsapi_remove.pp &lt;&lt;EOF\r\n$file = '\/home\/demo\/puppet-file-rsapi-remove'\r\nfile       {$file: ensure =&gt; present} -&gt;\r\nfile_rsapi {$file: ensure =&gt; absent}\r\nEOF\r\n\r\ndemo@85c63b50bfa3:~$ bin\/puppet apply --modulepath modules file_rsapi_remove.pp\r\nNotice: Compiled catalog for 85c63b50bfa3 in environment production in 0.03 seconds\r\nNotice: \/Stage[main]\/Main\/File[\/home\/demo\/puppet-file-rsapi-remove]\/ensure: created\r\nNotice: \/Stage[main]\/Main\/File_rsapi[\/home\/demo\/puppet-file-rsapi-remove]\/ensure: undefined 'ensure' from 'present'\r\nNotice: file_rsapi: Deleted: \/home\/demo\/puppet-file-rsapi-remove\r\n<\/pre>\n<h3>Thoughts<\/h3>\n<p>The apparent need for a <code>CanonicalString<\/code> masking as <code>String<\/code> makes it look like we are missing something. If the Resource-API only checked data types after canonicalization, we could make <code>get<\/code> return something simpler than <code>CanonicalString<\/code> to signal an already canonicalized value.<\/p>\n<p>The Resource-API&#8217;s default demand to return all existing resources simplifies development when one had this plan anyway. The low-level version of doing so is often a combination of <a href=\"https:\/\/www.rubydoc.info\/gems\/puppet\/Puppet\/Provider#prefetch-class_method\">prefetch<\/a> and <a href=\"https:\/\/www.rubydoc.info\/gems\/puppet\/Puppet\/Provider#instances-class_method\">instances<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Puppet is a software configuration management solution to manage IT infrastructure. One of the first things to be learnt about Puppet is its domain-specific language &#8211; the Puppet-DSL &#8211; and the concepts that come with it. Users can organize their code in classes and modules and use pre-defined resource types to manage resources like [&hellip;]<\/p>\n","protected":false},"author":20,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[2053,1885],"tags":[1808],"class_list":["post-8543","post","type-post","status-publish","format-standard","hentry","category-automation","category-howtos-en","tag-puppet-en"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v28.2 (Yoast SEO v28.3) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Puppet Custom Types and Providers - credativ\u00ae<\/title>\n<meta name=\"description\" content=\"Puppet Custom Types and Providers simplify the management of IT resources. Learn more about how to use them.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Puppet Custom Types and Providers\" \/>\n<meta property=\"og:description\" content=\"Puppet Custom Types and Providers simplify the management of IT resources. Learn more about how to use them.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/\" \/>\n<meta property=\"og:site_name\" content=\"credativ\u00ae\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/credativDE\/\" \/>\n<meta property=\"article:published_time\" content=\"2025-05-30T11:37:31+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-11-24T15:58:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.credativ.de\/wp-content\/uploads\/2019\/07\/Portfolio-Loesungen.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"800\" \/>\n\t<meta property=\"og:image:height\" content=\"550\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"dlu\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@credativde\" \/>\n<meta name=\"twitter:site\" content=\"@credativde\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"dlu\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"8 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/blog\\\/howtos-en\\\/puppet-custom-types-and-providers\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/blog\\\/howtos-en\\\/puppet-custom-types-and-providers\\\/\"},\"author\":{\"name\":\"dlu\",\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/#\\\/schema\\\/person\\\/752a786c601749e86be0210e2ccbf416\"},\"headline\":\"Puppet Custom Types and Providers\",\"datePublished\":\"2025-05-30T11:37:31+00:00\",\"dateModified\":\"2025-11-24T15:58:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/blog\\\/howtos-en\\\/puppet-custom-types-and-providers\\\/\"},\"wordCount\":1324,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/#organization\"},\"keywords\":[\"Puppet\"],\"articleSection\":[\"Automation\",\"HowTos\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.credativ.de\\\/en\\\/blog\\\/howtos-en\\\/puppet-custom-types-and-providers\\\/#respond\"]}],\"copyrightYear\":\"2025\",\"copyrightHolder\":{\"@id\":\"https:\\\/\\\/www.credativ.de\\\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/blog\\\/howtos-en\\\/puppet-custom-types-and-providers\\\/\",\"url\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/blog\\\/howtos-en\\\/puppet-custom-types-and-providers\\\/\",\"name\":\"Puppet Custom Types and Providers - credativ\u00ae\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/#website\"},\"datePublished\":\"2025-05-30T11:37:31+00:00\",\"dateModified\":\"2025-11-24T15:58:00+00:00\",\"description\":\"Puppet Custom Types and Providers simplify the management of IT resources. Learn more about how to use them.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/blog\\\/howtos-en\\\/puppet-custom-types-and-providers\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.credativ.de\\\/en\\\/blog\\\/howtos-en\\\/puppet-custom-types-and-providers\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/blog\\\/howtos-en\\\/puppet-custom-types-and-providers\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Puppet Custom Types and Providers\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/#website\",\"url\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/\",\"name\":\"credativ GmbH\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Organization\",\"Place\"],\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/#organization\",\"name\":\"credativ\u00ae\",\"url\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/\",\"logo\":{\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/blog\\\/howtos-en\\\/puppet-custom-types-and-providers\\\/#local-main-organization-logo\"},\"image\":{\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/blog\\\/howtos-en\\\/puppet-custom-types-and-providers\\\/#local-main-organization-logo\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/credativDE\\\/\",\"https:\\\/\\\/x.com\\\/credativde\",\"https:\\\/\\\/mastodon.social\\\/@credativde\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/credativ-gmbh\",\"https:\\\/\\\/www.instagram.com\\\/credativ\\\/\",\"https:\\\/\\\/www.northdata.de\\\/credativ GmbH, M\u00f6nchengladbach\\\/Amtsgericht Moenchengladbach HRB 23003\",\"https:\\\/\\\/github.com\\\/credativ\"],\"description\":\"credativ GmbH is an owner-managed, vendor-independent IT services and consulting company specializing in open source software, and one of the leading providers in this field. Founded in 1999, the company has been independently run by its management since March 2025 and is not part of any corporate group. We offer comprehensive, professional services ranging from consulting and infrastructure operations to 24\\\/7 support, custom solutions, and training. Our focus is on the holistic management of business-critical open source systems, including operating systems (e.g. Linux), databases (e.g. PostgreSQL), configuration management (e.g. Ansible, Puppet), and virtualization (e.g. Proxmox). As a committed member of the open source community, we help our customers use the benefits of free software securely, reliably, and efficiently in their IT environments.\",\"legalName\":\"credativ GmbH\",\"foundingDate\":\"1999-03-01\",\"duns\":\"316387060\",\"numberOfEmployees\":{\"@type\":\"QuantitativeValue\",\"minValue\":\"11\",\"maxValue\":\"50\"},\"address\":{\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/blog\\\/howtos-en\\\/puppet-custom-types-and-providers\\\/#local-main-place-address\"},\"geo\":{\"@type\":\"GeoCoordinates\",\"latitude\":\"51.1732374\",\"longitude\":\"6.392010099999999\"},\"telephone\":[\"+4921619174200\",\"08002733284\"],\"contactPoint\":{\"@type\":\"ContactPoint\",\"telephone\":\"08002733284\",\"email\":\"vertrieb@credativ.de\"},\"openingHoursSpecification\":[{\"@type\":\"OpeningHoursSpecification\",\"dayOfWeek\":[\"Monday\",\"Tuesday\",\"Wednesday\",\"Thursday\",\"Friday\"],\"opens\":\"09:00\",\"closes\":\"17:00\"},{\"@type\":\"OpeningHoursSpecification\",\"dayOfWeek\":[\"Saturday\",\"Sunday\"],\"opens\":\"00:00\",\"closes\":\"00:00\"}],\"email\":\"info@credativ.de\",\"areaServed\":\"D-A-CH\",\"vatID\":\"DE452151696\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/#\\\/schema\\\/person\\\/752a786c601749e86be0210e2ccbf416\",\"name\":\"dlu\"},{\"@type\":\"PostalAddress\",\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/blog\\\/howtos-en\\\/puppet-custom-types-and-providers\\\/#local-main-place-address\",\"streetAddress\":\"Hennes-Weisweiler-Allee 23\",\"addressLocality\":\"M\u00f6nchengladbach\",\"postalCode\":\"41179\",\"addressRegion\":\"Deutschland\",\"addressCountry\":\"DE\"},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.credativ.de\\\/en\\\/blog\\\/howtos-en\\\/puppet-custom-types-and-providers\\\/#local-main-organization-logo\",\"url\":\"https:\\\/\\\/www.credativ.de\\\/wp-content\\\/uploads\\\/2025\\\/04\\\/credativ-logo-right.svg\",\"contentUrl\":\"https:\\\/\\\/www.credativ.de\\\/wp-content\\\/uploads\\\/2025\\\/04\\\/credativ-logo-right.svg\",\"caption\":\"credativ\u00ae\"}]}<\/script>\n<meta name=\"geo.placename\" content=\"M\u00f6nchengladbach\" \/>\n<meta name=\"geo.position\" content=\"51.1732374;6.392010099999999\" \/>\n<meta name=\"geo.region\" content=\"Germany\" \/>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Puppet Custom Types and Providers - credativ\u00ae","description":"Puppet Custom Types and Providers simplify the management of IT resources. Learn more about how to use them.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/","og_locale":"en_US","og_type":"article","og_title":"Puppet Custom Types and Providers","og_description":"Puppet Custom Types and Providers simplify the management of IT resources. Learn more about how to use them.","og_url":"https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/","og_site_name":"credativ\u00ae","article_publisher":"https:\/\/www.facebook.com\/credativDE\/","article_published_time":"2025-05-30T11:37:31+00:00","article_modified_time":"2025-11-24T15:58:00+00:00","og_image":[{"width":800,"height":550,"url":"https:\/\/www.credativ.de\/wp-content\/uploads\/2019\/07\/Portfolio-Loesungen.jpg","type":"image\/jpeg"}],"author":"dlu","twitter_card":"summary_large_image","twitter_creator":"@credativde","twitter_site":"@credativde","twitter_misc":{"Written by":"dlu","Est. reading time":"8 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/#article","isPartOf":{"@id":"https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/"},"author":{"name":"dlu","@id":"https:\/\/www.credativ.de\/en\/#\/schema\/person\/752a786c601749e86be0210e2ccbf416"},"headline":"Puppet Custom Types and Providers","datePublished":"2025-05-30T11:37:31+00:00","dateModified":"2025-11-24T15:58:00+00:00","mainEntityOfPage":{"@id":"https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/"},"wordCount":1324,"commentCount":0,"publisher":{"@id":"https:\/\/www.credativ.de\/en\/#organization"},"keywords":["Puppet"],"articleSection":["Automation","HowTos"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/#respond"]}],"copyrightYear":"2025","copyrightHolder":{"@id":"https:\/\/www.credativ.de\/#organization"}},{"@type":"WebPage","@id":"https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/","url":"https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/","name":"Puppet Custom Types and Providers - credativ\u00ae","isPartOf":{"@id":"https:\/\/www.credativ.de\/en\/#website"},"datePublished":"2025-05-30T11:37:31+00:00","dateModified":"2025-11-24T15:58:00+00:00","description":"Puppet Custom Types and Providers simplify the management of IT resources. Learn more about how to use them.","breadcrumb":{"@id":"https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.credativ.de\/en\/"},{"@type":"ListItem","position":2,"name":"Puppet Custom Types and Providers"}]},{"@type":"WebSite","@id":"https:\/\/www.credativ.de\/en\/#website","url":"https:\/\/www.credativ.de\/en\/","name":"credativ GmbH","description":"","publisher":{"@id":"https:\/\/www.credativ.de\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.credativ.de\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Organization","Place"],"@id":"https:\/\/www.credativ.de\/en\/#organization","name":"credativ\u00ae","url":"https:\/\/www.credativ.de\/en\/","logo":{"@id":"https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/#local-main-organization-logo"},"image":{"@id":"https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/#local-main-organization-logo"},"sameAs":["https:\/\/www.facebook.com\/credativDE\/","https:\/\/x.com\/credativde","https:\/\/mastodon.social\/@credativde","https:\/\/www.linkedin.com\/company\/credativ-gmbh","https:\/\/www.instagram.com\/credativ\/","https:\/\/www.northdata.de\/credativ GmbH, M\u00f6nchengladbach\/Amtsgericht Moenchengladbach HRB 23003","https:\/\/github.com\/credativ"],"description":"credativ GmbH is an owner-managed, vendor-independent IT services and consulting company specializing in open source software, and one of the leading providers in this field. Founded in 1999, the company has been independently run by its management since March 2025 and is not part of any corporate group. We offer comprehensive, professional services ranging from consulting and infrastructure operations to 24\/7 support, custom solutions, and training. Our focus is on the holistic management of business-critical open source systems, including operating systems (e.g. Linux), databases (e.g. PostgreSQL), configuration management (e.g. Ansible, Puppet), and virtualization (e.g. Proxmox). As a committed member of the open source community, we help our customers use the benefits of free software securely, reliably, and efficiently in their IT environments.","legalName":"credativ GmbH","foundingDate":"1999-03-01","duns":"316387060","numberOfEmployees":{"@type":"QuantitativeValue","minValue":"11","maxValue":"50"},"address":{"@id":"https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/#local-main-place-address"},"geo":{"@type":"GeoCoordinates","latitude":"51.1732374","longitude":"6.392010099999999"},"telephone":["+4921619174200","08002733284"],"contactPoint":{"@type":"ContactPoint","telephone":"08002733284","email":"vertrieb@credativ.de"},"openingHoursSpecification":[{"@type":"OpeningHoursSpecification","dayOfWeek":["Monday","Tuesday","Wednesday","Thursday","Friday"],"opens":"09:00","closes":"17:00"},{"@type":"OpeningHoursSpecification","dayOfWeek":["Saturday","Sunday"],"opens":"00:00","closes":"00:00"}],"email":"info@credativ.de","areaServed":"D-A-CH","vatID":"DE452151696"},{"@type":"Person","@id":"https:\/\/www.credativ.de\/en\/#\/schema\/person\/752a786c601749e86be0210e2ccbf416","name":"dlu"},{"@type":"PostalAddress","@id":"https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/#local-main-place-address","streetAddress":"Hennes-Weisweiler-Allee 23","addressLocality":"M\u00f6nchengladbach","postalCode":"41179","addressRegion":"Deutschland","addressCountry":"DE"},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.credativ.de\/en\/blog\/howtos-en\/puppet-custom-types-and-providers\/#local-main-organization-logo","url":"https:\/\/www.credativ.de\/wp-content\/uploads\/2025\/04\/credativ-logo-right.svg","contentUrl":"https:\/\/www.credativ.de\/wp-content\/uploads\/2025\/04\/credativ-logo-right.svg","caption":"credativ\u00ae"}]},"geo.placename":"M\u00f6nchengladbach","geo.position":{"lat":"51.1732374","long":"6.392010099999999"},"geo.region":"Germany"},"permalink_manager":null,"_links":{"self":[{"href":"https:\/\/www.credativ.de\/en\/wp-json\/wp\/v2\/posts\/8543","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.credativ.de\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.credativ.de\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.credativ.de\/en\/wp-json\/wp\/v2\/users\/20"}],"replies":[{"embeddable":true,"href":"https:\/\/www.credativ.de\/en\/wp-json\/wp\/v2\/comments?post=8543"}],"version-history":[{"count":60,"href":"https:\/\/www.credativ.de\/en\/wp-json\/wp\/v2\/posts\/8543\/revisions"}],"predecessor-version":[{"id":9108,"href":"https:\/\/www.credativ.de\/en\/wp-json\/wp\/v2\/posts\/8543\/revisions\/9108"}],"wp:attachment":[{"href":"https:\/\/www.credativ.de\/en\/wp-json\/wp\/v2\/media?parent=8543"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.credativ.de\/en\/wp-json\/wp\/v2\/categories?post=8543"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.credativ.de\/en\/wp-json\/wp\/v2\/tags?post=8543"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}