if tagged scope and autoconfig module

68 views
Skip to first unread message

Poil

unread,
Dec 1, 2014, 3:21:01 AM12/1/14
to puppet...@googlegroups.com
Hi,

I try to write an autoconfigure module (collectd) that depends on other modules included for my node.

So I've this class autoloaded for all my nodes :

class collectd::client::autoconfig {
  require collectd::client

  if tagged('apache') {
    require apache
    include collectd::client::plugin::apache
  }
  if tagged('memcached') {
    require memcached
    include collectd::client::plugin::memcached
  }
(...)
}

But sometimes, on my old nodes, collectd is manually configured (for an historical bad/good reason); so, for my old_node1 I have "include collectd::client::plugin::apache" but I don't have the "apache module" included; in this case my autoloader will detect the tag "apache" in my class "include collectd::client::plugin::apache" ...

It does not seem possible to use "::apache" in the tagged function ? Am I right ?
Have you got any idea for my case ? (I can add a parameter to disable my autoconfig, but I'm looking for a better solution if it exists.)

Best regards,
Poil

jcbollinger

unread,
Dec 1, 2014, 10:15:04 AM12/1/14
to puppet...@googlegroups.com


On Monday, December 1, 2014 2:21:01 AM UTC-6, Poil wrote:
Hi,

I try to write an autoconfigure module (collectd) that depends on other modules included for my node.


If you mean you are writing a module containing declarations that are conditioned on whether other classes or modules have been assigned to the target node's catalog, then my first and best advice is to abandon that idea altogether.  As a general rule, such declarations do not work reliably or well because the results of the necessary conditional tests depend on the order in which they are evaluated relative to other declarations in other manifests, and that order is very difficult to predict.  Instead, use Hiera or an ENC to dynamically specify the needed classes for each node.

 

So I've this class autoloaded for all my nodes :


In Puppet jargon term "autoloading" refers to the mechanism by which the catalog compiler locates the manifest file in which the definition of a given class or defined type resides (supposing that you have followed the layout conventions that allow it to do so).  That doesn't seem to be what you mean by the term, so if your meaning matters to the question then it would be to your advantage to explain.

 

class collectd::client::autoconfig {
  require collectd::client

  if tagged('apache') {
    require apache
    include collectd::client::plugin::apache
  }
  if tagged('memcached') {
    require memcached
    include collectd::client::plugin::memcached
  }
(...)
}

But sometimes, on my old nodes, collectd is manually configured (for an historical bad/good reason); so, for my old_node1 I have "include collectd::client::plugin::apache" but I don't have the "apache module" included; in this case my autoloader will detect the tag "apache" in my class "include collectd::client::plugin::apache" ...

It does not seem possible to use "::apache" in the tagged function ? Am I right ?
Have you got any idea for my case ? (I can add a parameter to disable my autoconfig, but I'm looking for a better solution if it exists.)



I haven't used tagged() much (or at all, really), but I don't think it does what you seem to think it does. The docs say it "tells you whether the current container is tagged with the specified tags", where the usual meaning of "container" in Puppet is a class, node, or catalog, so that function will not inform directly about whether a class other than the one in which the function call appears is tagged with the specified tag. Because objects inherit tags from their containers, however, it will indirectly inform about whether the object in which the call appears is declared by a class bearing the specified tag (recursively).  Classes can be declared multiple times in multiple places, however, so I cannot see how the function result could fail to be evaluation-order dependent.

In any case, be aware that classes are automatically tagged with every segment of their own qualified names, so yes, class collectd::client:plugin::apache will always bear the tags "collectd", "client", "plugin", and "apache" (among others) as will every object declared by that class.  I wouldn't expect the tagged() function to reject the tag "::apache", but that particular tag won't be present on any object unless you've set it explicitly.  None of that should not directly affect class collectd::client::autoconfig, though.  The only way it should see a tag 'apache' is if it is declared within a container bearing that tag.  Do you mean to say that class collectd::client:plugin::apache declares class collectd::client::autoconfig?  If so, why?


John

Poil

unread,
Dec 8, 2014, 6:26:05 AM12/8/14
to puppet...@googlegroups.com
Hey thanks,

I don't understand : in template we can use "classes" that contain all defined classes.
Is it a good idea to add

if defined(Class['::apache']) {
  require apache; 
  include collectd::apache
}

Or is a class is like other ressource, only defined when parsed ? (so could be randomly defined when playing collectd::autoconfig)

For the moment I've invert my code
I have a class apache::collectd which call collectd::apache with an hash as parameter

Best regards,
--
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/1f68a3ab-4976-470b-828c-2c36981d66f4%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

jcbollinger

unread,
Dec 8, 2014, 10:12:49 AM12/8/14
to puppet...@googlegroups.com


On Monday, December 8, 2014 5:26:05 AM UTC-6, Poil wrote:
Hey thanks,

I don't understand : in template we can use "classes" that contain all defined classes.


That is potentially misleading: the 'classes' variable can give you all classes that have been declared up to that point in the course of evaluating manifests to build the target node's catalog.  There is no way to reliably get a list that correctly anticipates all classes that ultimately will be present in the final catalog, and it is very difficult to predict the the order in which classes will be added, so 'classes' is far less useful than it may sound.  Even if you find that it works as you want for some nodes, with your current manifest set, it is extremely difficult to ensure or prove that it will work as you want for all nodes as your manifest set evolves.  When it fails, it will fail silently.

 
Is it a good idea to add

if defined(Class['::apache']) {
  require apache; 
  include collectd::apache
}



No, it is a terrible idea.  The 'defined()' function has the same kind of evaluation-order dependency that templates' 'classes' variable has.  It should never be used to check whether classes or resources have been declared, and in fact there has been serious consideration of deprecating that aspect of its behavior.

You can use defined to test whether a class or resource type is available to be declared, but that requires a different form for the argument.  Moreover, whether a class is available is a static characteristic of your manifest set, so it is of comparatively little value to test that dynamically.

 
Or is a class is like other ressource, only defined when parsed ? (so could be randomly defined when playing collectd::autoconfig)



The terms "defined" and "declared" are rather slippery here, and the 'defined()' function unhelpfully conflates their different meanings.  A resource type is "defined" if Puppet knows that type, either because it is a plugin type that is available to the catalog builder (built-in or from a module), or because it is a defined type whose definition has been evaluated or can be located by the autoloader.  Similarly, a class is "defined" if its definition (body) has been evaluated or can be located by the autoloader.  The use of 'defined()' to test these conditions is not dangerous or harmful.

On the other hand, a "resource" in the sense of an instance of a given resource type comes into existence only when Puppet evaluates a declaration of it.  Before completion of catalog building, it is impossible to predict which additional resources will be declared during the remainder of the catalog building process.  Similarly, a defined class will be present in the target node's catalog only after Puppet evaluates a declaration of it (an 'include', 'require', or 'contain' call, or a resource-style class declaration).  Before completion of catalog building, it is impossible to predict which additional classes will be declared during the remainder of the catalog building process.

People who are unfamiliar with these details are prone to suppose that 'defined()' can inform about all the classes and/or resources in the final catalog, but that is impossible.  Parser functions are called as part of catalog building, and the desired information is not available until catalog building is complete.

 
For the moment I've invert my code
I have a class apache::collectd which call collectd::apache with an hash as parameter



If that does the job you want it to do then it's fine.  You risk trouble with it, however, if class collectd::apache is declared anywhere else.  If you use a resource-style class declaration (as you must to specify any class parameters via your manifests) then that class declaration must be the first declaration of that class that is evaluated (else catalog building will fail).  We have already covered that evaluation order is difficult to predict.  If you have multiple declarations of a given class, then it is difficult to control the order in which they are evaluated.

If you need to dynamically determine which classes to declare, then the general solution is to decouple that determination from Puppet's evaluator: use an ENC or hiera to tell Puppet which classes are needed, and what their parameters should be.


John

Reply all
Reply to author
Forward
0 new messages