{"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,"_improvement_type_select":"improve_an_existing","_thumb_yes_seoaic":false,"_frame_yes_seoaic":false,"seoaic_generate_description":"","seoaic_improve_instructions_prompt":"","seoaic_rollback_content_improvement":"","seoaic_idea_thumbnail_generator":"","thumbnail_generated":false,"thumbnail_generate_prompt":"","seoaic_article_description":"","seoaic_article_subtitles":[],"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 v27.5 (Yoast SEO v27.5) - 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\\\/\"],\"description\":\"Die credativ GmbH ist ein f\u00fchrendes, auf Open Source Software spezialisiertes IT-Dienstleistungs- und Beratungsunternehmen. Wir bieten umfassende und professionelle Services, von Beratung und Infrastruktur-Betrieb \u00fcber 24\\\/7 Support bis hin zu individuellen L\u00f6sungen und Schulungen. Unser Fokus liegt auf dem ganzheitlichen Management von gesch\u00e4ftskritischen Open-Source-Systemen, darunter Betriebssysteme (z.B. Linux), Datenbanken (z.B. PostgreSQL), Konfigurationsmanagement (z.B. Ansible, Puppet) und Virtualisierung. Als engagierter Teil der Open-Source-Community unterst\u00fctzen wir unsere Kunden dabei, die Vorteile freier Software sicher, stabil und effizient in ihrer IT-Umgebung zu nutzen.\",\"legalName\":\"credativ GmbH\",\"foundingDate\":\"2025-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\/"],"description":"Die credativ GmbH ist ein f\u00fchrendes, auf Open Source Software spezialisiertes IT-Dienstleistungs- und Beratungsunternehmen. Wir bieten umfassende und professionelle Services, von Beratung und Infrastruktur-Betrieb \u00fcber 24\/7 Support bis hin zu individuellen L\u00f6sungen und Schulungen. Unser Fokus liegt auf dem ganzheitlichen Management von gesch\u00e4ftskritischen Open-Source-Systemen, darunter Betriebssysteme (z.B. Linux), Datenbanken (z.B. PostgreSQL), Konfigurationsmanagement (z.B. Ansible, Puppet) und Virtualisierung. Als engagierter Teil der Open-Source-Community unterst\u00fctzen wir unsere Kunden dabei, die Vorteile freier Software sicher, stabil und effizient in ihrer IT-Umgebung zu nutzen.","legalName":"credativ GmbH","foundingDate":"2025-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"},"_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}]}}