validating array property in custom_type

173 views
Skip to first unread message

jwil...@gmail.com

unread,
Aug 7, 2015, 4:48:33 PM8/7/15
to Puppet Users
Hi all!

I'm having trouble with a custom type's type-wide validate call.  I've done a lot of digging into the Puppet documentation and a lot of Googling and haven't found a lot of guidance.   My Puppet version is 3.7.5.

Basically, I have a property defined like this in my type:

newproperty(:servers,:array_matching=>:all) do
    desc "List of database servers; first server in the list will be considered the primary server"

    isrequired
    def insync?(is)
      return false unless is == should
      true
    end

end

I want to check that the array is non-empty.   I figured out if I specify a validate block inside of the newproperty block then I'll just get each individual array member, one at a time, which isn't what I want.   So, instead, I implemented a type-wide validate call like this:

Puppet::Type.newtype(:my_type) do

    validate do
               fail("servers should have at least one member")  if self[:servers].size == 0
    done


When I try to run puppet resource my_type, I get:

Error: Could not run: undefined method `size' for nil:NilClass

When I do a pp on self, I get something that looks like (in part):

#<Puppet::Type::My_type:0x000000035f7528
 @managed=false,
 @name_var_cache=:name,
 @original_parameters=
  {:provider=>
    #<Puppet::Type::My_type::ProviderMy_type:0x000000035d1350
     @property_flush={},
     @property_hash=
      {
       :servers=>["db1"],
       },
     @resource=#<Puppet::Type::My_type:0x000000035f7528 ...>>},
 @parameters=
  {
   *snip*
},
 @provider=
  #<Puppet::Type::My_type::ProviderMy_type:0x000000035d1350
   @property_flush={},
   @property_hash=
    {
     :servers=>["db1"],
     },
   @resource=#<Puppet::Type::My_type:0x000000035f7528 ...>>,
 @tags=
  #<Puppet::Util::TagSet: {"my_type",
   "mytitle"}>,
 @title="mytitle">

I poked around the types provided by Puppet and it looks like I should be able to do

self[:servers]  

to access the property, but in practice that doesn't seem to work.  It looks like the data I want is buried in the object, but I'm not sure of the correct means to get at it. 



Hunter Haugen

unread,
Aug 11, 2015, 2:27:06 PM8/11/15
to puppet...@googlegroups.com
On Fri, Aug 7, 2015 at 1:48 PM <jwil...@gmail.com> wrote:
Hi all!

I'm having trouble with a custom type's type-wide validate call.  I've done a lot of digging into the Puppet documentation and a lot of Googling and haven't found a lot of guidance.   My Puppet version is 3.7.5.

Basically, I have a property defined like this in my type:

newproperty(:servers,:array_matching=>:all) do
    desc "List of database servers; first server in the list will be considered the primary server"

    isrequired
    def insync?(is)
      return false unless is == should
      true
    end

end

I want to check that the array is non-empty.   I figured out if I specify a validate block inside of the newproperty block then I'll just get each individual array member, one at a time, which isn't what I want.   So, instead, I implemented a type-wide validate call like this:

Puppet::Type.newtype(:my_type) do

    validate do
               fail("servers should have at least one member")  if self[:servers].size == 0
    done

It's kind of awkward, but if your type uses self.instances, then in the validate block is run after self.instances runs and has resource.provider.servers but not self[:servers]. So I usually just do something like `if self[:servers] and self[:servers].size == 0` to avoid validating self.instances stuff.

The validate block is then run again when each resource is evaluated, and that is when self's hash is populated with values from the catalog.
--
You received this message because you are subscribed to the Google Groups "Puppet Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to puppet-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/puppet-users/5d8ea6d1-1afd-4983-a059-832d238eb6fa%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

jwil...@gmail.com

unread,
Aug 11, 2015, 3:35:13 PM8/11/15
to Puppet Users
Okay, so if I understand you right, validate gets called twice:  once without the self hash initialized and once with it initialized.   Seems weird, but I can follow that.    So, I might want to do something like:

if(self[:servers])
   fail("Servers must be an array with at least one member") unless self[:servers].is_a?(Array) and self[:servers].size > 0
end

Here's a follow-up then:  how can you tell the difference between self just not being initialized and the user failing to specify the value for a particular property?  I guess I'm mitigating it somewhat by doing:


newproperty(:servers,:array_matching=>:all) do
    desc "List of database servers; first server in the list will be considered the primary server"

    isrequired
    def insync?(is)
      return false unless is == should
      true
    end

  end

And properties that aren't required wouldn't need to differentiate between not being set and just not initialized yet.

jwil...@gmail.com

unread,
Aug 11, 2015, 3:57:28 PM8/11/15
to Puppet Users
So, found another wrinkle in this:

If the resource doesn't exist on the system yet, and I leave the servers attribute out, my validate doesn't catch it:

mytype{'test type':
# servers => ['server1','server2']
}

That should fail the catalog compile since servers is required and it isn't set.  My validate code looks like:

validate do

     if(self[:servers])
           fail("Servers must be an array with at least one member") unless self[:servers].is_a?(Array) and self[:servers].size > 0
      end
end

I found out that isrequired in the newproperty block doesn't actually do anything, so I need some way to detect that the resource doesn't exist yet, but the catalog specified by the user is wrong.

Hunter Haugen

unread,
Aug 11, 2015, 4:58:44 PM8/11/15
to puppet...@googlegroups.com
On Tue, Aug 11, 2015 at 12:57 PM <jwil...@gmail.com> wrote:
So, found another wrinkle in this:

If the resource doesn't exist on the system yet, and I leave the servers attribute out, my validate doesn't catch it:

mytype{'test type':
# servers => ['server1','server2']
}

That should fail the catalog compile since servers is required and it isn't set.  My validate code looks like:

validate do

     if(self[:servers])
           fail("Servers must be an array with at least one member") unless self[:servers].is_a?(Array) and self[:servers].size > 0
      end
end

I found out that isrequired in the newproperty block doesn't actually do anything, so I need some way to detect that the resource doesn't exist yet, but the catalog specified by the user is wrong.



Ah yes, the old "no way to specify required attributes" thing. This should work for required properties:

fail('... is required') if ! self[:servers] and ! self.provider.servers

And this should work for required parameters or properties:

fail('... is required') if ! self[:servers] and self[:ensure] != :absent


On Tuesday, August 11, 2015 at 3:35:13 PM UTC-4, jwil...@gmail.com wrote:
Okay, so if I understand you right, validate gets called twice:  once without the self hash initialized and once with it initialized.   Seems weird, but I can follow that.    So, I might want to do something like:


If memory serves me, in order of operation it gets called by the compiler to validate resources being added to the catalog during compile (to catch early validation failures), then it gets called during catalog application to validate resources returned by self.prefetch (or self.instances if running `puppet resource`) to make sure the system returned things that make sense, and then finally called to validate each resource being evaluated from the catalog during apply, because some things can only be validated agent-side.
Reply all
Reply to author
Forward
0 new messages