Override param from parametrized class?

2,528 views
Skip to first unread message

Jakov Sosic

unread,
Sep 5, 2012, 2:19:07 PM9/5/12
to Puppet Users
Hi.

I've seen in many modules at puppet-forge this kind of organization:

class myclass(
$somevar='value'
) inherits myclass::params {
file { $mydir:
ensure => directory,
}
}

class myclass::params {
$mydir = '/some/path'
}


Now, I wonder how can I override $mydir from node definition? Or am I
missing the whole point, am I supposed to put $mydir inside standard
brackets and then call the class with something like:

node mynode.mydomain.com {
class myclass($mydir='/some/other/path')
}


Thank you

jcbollinger

unread,
Sep 5, 2012, 5:37:35 PM9/5/12
to puppet...@googlegroups.com


On Wednesday, September 5, 2012 1:19:16 PM UTC-5, Jakov Sosic wrote:
Hi.

I've seen in many modules at puppet-forge this kind of organization:

class myclass(
  $somevar='value'
) inherits myclass::params {
  file { $mydir:
    ensure => directory,
  }
}

class myclass::params {
  $mydir = '/some/path'
}


Now, I wonder how can I override $mydir from node definition?


Not as your example is written, read on.

 
Or am I
missing the whole point, am I supposed to put $mydir inside standard
brackets and then call the class with something like:

node mynode.mydomain.com {
  class myclass($mydir='/some/other/path')
}


I think very likely have overlooked a key aspect of the pattern you observed (or else the modules you are looking at aren't good teaching examples).  The usual usage of the pattern I suspect you've seen is more like this:

# This might as readily be named mymodule::myclass
class mymodule ($myparam = $myparam_default) inherits mymodule::params {
  file { $myparam:
    # ...
  }
}

class mymodule::params {
  $myparam_default = '/some/path'
}


The key difference there is that a variable declared by the ::params class is used as a default value for a parameter of the class that inherits it.  That's the only reason why class inheritance is reasonable in this particular case.  As a matter of good style, classes inherited for this purpose should normally contain only variable declarations.

If one class wants to use another class's variables, but not as class parameter defaults, then it should not inherit from that class.  Instead, it should 'include' that class (provided it is not parametrized) and then refer to the variables by their fully-qualified names, such as $mymodule::params::anothervar.  Inheriting the ::params class is simply a trick to ensure the variables are initialized and accessible for use before any part of the class body of the inheriting class is parsed (i.e. in time to be used as parameter defaults).

Getting back to your questions, no, you cannot override the value of a class variable, but you can provide your own values for class parameters, which will then be used instead of whatever default values may have been declared.  That's done like so:

class { "mymodule":
  # parameters follow...
  myparam => 'custom value'
}

Yes, it looks very much like a resource declaration.  No, I don't think that was a good design decision, but the prevailing opinion at PuppetLabs is different, or was when parametrized classes were added.

Before you start swooning over how nice parametrized classes look, let me warn you that they carry some substantial drawbacks.  Therefore, I urge you to avoid using them in your own code as much as you can do, at least until you understand Puppet well enough to appreciate why they might make sense in some circumstances (clue me in when you figure that out), and especially until you appreciate the problems they present.  Hint: the biggest one is that parametrized classes cannot be declared more than once.

There are other, better ways to get data to your classes, notably data access functions such as hiera(), and global or [other]class variables.  Puppet 3 does shine up parametrized classes a lot, in large part by integrating hiera directly into parameter value resolution.  Nevertheless, you don't need class parameters, and even in Puppet 3, parametrized classes retain a few pointy bits.  Therefore, I'd focus on building classes that rely on hiera (which for Puppet 2.x is a third-party extension) where they need external data, as that will serve you well both now and going forward.


John

Jakov Sosic

unread,
Sep 7, 2012, 7:24:54 AM9/7/12
to puppet...@googlegroups.com
On 09/05/2012 11:37 PM, jcbollinger wrote:

> class { "mymodule":
> # parameters follow...
> myparam => 'custom value'
> }
>
> Yes, it looks very much like a resource declaration. No, I don't think
> that was a good design decision, but the prevailing opinion at
> PuppetLabs is different, or was when parametrized classes were added.

It's a shame you cannot define multiple instances of parametrized
classes with different parameters :(

Anyway, how should I approach adding dynamic content into node's
manifest, using a define maybe?

Davide Ferrari

unread,
Sep 7, 2012, 9:03:26 AM9/7/12
to puppet...@googlegroups.com
On 05/09/12 23:37, jcbollinger wrote:
> Before you start swooning over how nice parametrized classes look, let
> me warn you that they carry some substantial drawbacks. Therefore, I
> urge you to avoid using them in your own code as much as you can do,
> at least until you understand Puppet well enough to appreciate why
> they might make sense in some circumstances (clue me in when you
> figure that out), and especially until you appreciate the problems
> they present. Hint: the biggest one is that parametrized classes
> cannot be declared more than once.
I've raised this very topic on the IRC channel, due to my frustration
with the *bug* [1] that makes impossible to override parameters with
custom classes while inheriting, and I still don't understand *why* is
inheritance so bad nad if it's so, why is it still there? I think
inheritance improves a lot manifests/classes readability and personally
I hate using global variables and passing parameters (but it's just a
matter of taste).

But hey, if there's any technical reason in Puppet that makes
inheritance bad, I'm all ears and happy to change my mind about it :)

[1] https://projects.puppetlabs.com/issues/5517

--
Davide Ferrari
Senior System Administrator
Trovit Search http://www.trovit.com

jcbollinger

unread,
Sep 7, 2012, 9:20:33 AM9/7/12
to puppet...@googlegroups.com


On Friday, September 7, 2012 6:25:11 AM UTC-5, Jakov Sosic wrote:
On 09/05/2012 11:37 PM, jcbollinger wrote:

> class { "mymodule":
>   # parameters follow...
>   myparam => 'custom value'
> }
>
> Yes, it looks very much like a resource declaration.  No, I don't think
> that was a good design decision, but the prevailing opinion at
> PuppetLabs is different, or was when parametrized classes were added.

It's a shame you cannot define multiple instances of parametrized
classes with different parameters :(


No, that's not it.  I mean you can't do that, but the real problem is that you can't declare the same parametrized class multiple times even with the same parameters.  That's one of the things you can do with ordinary classes (with the same effect as declaring that class exactly once), and there are several circumstances where it's a useful ability to have.

Classes are singletons, so it doesn't make sense to try to assign different sets of parameters in different places.  If you are trying to model some aspect of your systems that may be present more than once, then it's certainly a resource, and it should be modeled via one of Puppets built-in resources or via a resource of a custom or defined type.  Most of the time you can use a defined type.
 

Anyway, how should I approach adding dynamic content into node's
manifest, using a define maybe?


That's what I addressed at the end of my previous message.  I strongly recommend that you look into hiera.  Adding to what I already said, however, resources of defined types are just fine when a resource (as opposed to a class) is in fact what you want.  It is worthwhile to understand the difference between class and resource at the conceptual level, because there are some circumstances that require a class.  You may save yourself future grief if you start out with classes where they're appropriate, even if, for the moment, they're not actually needed.


John


Jakov Sosic

unread,
Sep 7, 2012, 11:02:42 AM9/7/12
to puppet...@googlegroups.com
On 09/07/2012 03:20 PM, jcbollinger wrote:

> That's what I addressed at the end of my previous message. I strongly
> recommend that you look into hiera. Adding to what I already said,
> however, resources of defined types are just fine when a resource (as
> opposed to a class) is in fact what you want. It is worthwhile to
> understand the difference between class and resource at the conceptual
> level, because there are some circumstances that require a class. You
> may save yourself future grief if you start out with classes where
> they're appropriate, even if, for the moment, they're not actually needed.

I don't think you understood me there :) I use hiera for some purposes,
but this is my class:

class cobbler (
$service_name = $cobbler::params::service_name,
$package_name = $cobbler::params::package_name,
$package_ensure = $cobbler::params::package_ensure,
$distro_path = $cobbler::params::distro_path,
) inherits cobbler::params {
package { $package_name :
ensure => $package_ensure,
}
service { $service_name :
ensure => running,
enable => true,
require => Package[$package_name],
}
file { $distro_path :
ensure => directory,
owner => root,
group => root,
mode => '0755',
}
file { '/etc/cobbler/settings':
ensure => present,
owner => root,
group => root,
mode => '0644',
content => template('cobbler/settings.erb'),
notify => Service["$service_name"],
}
define add_distro ($arch,$isolink){
$distro = $title
cobblerdistro { $distro :
ensure => present,
arch => $arch,
isolink => $isolink,
destdir => $cobbler::distro_path,
require => Service[$cobbler::service_name],
}
}
define del_distro (){
$distro = $title
cobblerdistro { $distro :
ensure => absent,
destdir => $cobbler::distro_path,
require => Service[$cobbler::service_name],
}
}
}


So, from node manifest, I can do this:
node 'mynode' {
class { 'cobbler':
distro_path => '/data/distro'
}
cobbler::add_distro { 'CentOS-6.3-x86_64':
arch => 'x86_64',
isolink =>
'http://mi.mirror.garr.it/mirrors/CentOS/6.3/isos/x86_64/CentOS-6.3-x86_64-bin-DVD1.iso',
}
cobbler::add_distro { 'CentOS-6.2-x86_64':
arch => 'x86_64',
isolink =>
'http://mi.mirror.garr.it/mirrors/CentOS/6.2/isos/x86_64/CentOS-6.2-x86_64-bin-DVD1.iso',
}
}


Is this OK?

jcbollinger

unread,
Sep 10, 2012, 9:34:33 AM9/10/12
to puppet...@googlegroups.com


It depends on what you mean by "OK".  Your class is not consistent with my recommendations, but that doesn't make it wrong.

I am recommending that you use only hiera to communicate site- and node-specific data to your classes.  That is, according to me, you should avoid use of parametrized classes wherever it is feasible to do so.  That would mean using parametrized classes only from third-party modules, really, since you can always avoid parametrization in classes you write yourself.  Thus, your "cobbler" class might start this way:

class cobbler {
  include 'cobbler::params'

  $service_name = hiera('cobbler_service_name', $cobbler::params::service_name)
  $package_name = hiera('cobbler_package_name', $cobbler::params::package_name)
  $package_ensure = hiera('cobbler_package_ensure', $cobbler::params::package_ensure)
  $distro_path = hiera('cobbler_distro_path', $cobbler::params::distro_path)

  # ...


Voila, (the beginning of) a non-parametrized version of your class.  Nothing else has to change (but see below).  Note that the non-parametrized version also doesn't need the inheritance trick; it uses an ordinary 'include' instead.

On a separate note, the PuppetLabs style recommendation and prevailing opinion agree that type definitions should go into their own files.  Thus, instead of add_distro() and del_distro() being defined in the body of class cobbler, they should appear in sibling files add_distro.pp and del_distro.pp.  And as a side note, that will actually be much cleaner with the non-parametrized version of class cobbler than it would be with the parametrized version, because
  1. It is safe for the definition bodies (in a separate file) to refer to class variables, but not to class parameters (the latter fails in some Puppet versions).
  2. The definition bodies can 'include' class cobbler to ensure that its variables are recognized (which they must not do if 'cobbler' is parametrized, even if they know the correct parameters, because parametrized classes can be declared at most once)
  3. If you do (2), then, at your option, your nodes don't have to declare class 'cobbler' at all as long as they declare at least one cobbler::add_distro or cobbler::del_distro resource
So, for instance:

modules/cobbler/manifests/add_distro.pp:

define add_distro ($arch, $isolink) {
  include 'cobbler'

  cobblerdistro { $title :
    ensure  => present,
    arch    => $arch,
    isolink => $isolink,
    destdir => $cobbler::distro_path,
    require => Service[$cobbler::service_name],
  }
}


John

jcbollinger

unread,
Sep 10, 2012, 10:01:43 AM9/10/12
to puppet...@googlegroups.com


On Monday, September 10, 2012 8:34:33 AM UTC-5, jcbollinger wrote:

modules/cobbler/manifests/add_distro.pp:

define add_distro ($arch, $isolink) {


Oops, that should be

define cobbler::add_distro ($arch, $isolink) {
#...


John

Jakov Sosic

unread,
Sep 11, 2012, 12:59:52 PM9/11/12
to puppet...@googlegroups.com
Thank you for your insights, very helpful!

Reply all
Reply to author
Forward
0 new messages