Bug #5517

behavior change within 2.6 makes it impossible to override class parameters of "included" parametrized classes

Added by Peter Meier over 1 year ago. Updated 27 days ago.

Status:Accepted Start date:12/12/2010
Priority:High Due date:
Assignee:Nick Lewis % Done:

0%

Category:language
Target version:2.7.x
Affected Puppet version:2.6.3 Branch:
Keywords:parameterized_classes
Votes: 14

Description

In 2.6.1 the following recipe:

class a(
  $b_c = { 'b' => 'foo' }
) {
  notice $a::b_c
  if $a::b_c {
    notice $a::b_c['b']
  }
}

class b {
  class{'a': b_c => false }
}

class b::c inherits b {
  Class['a']{ b_c => { 'b' => 'bleh' } }
}

class b::d {
  include ::b::c
}

include b::d

produces the following output:

$ puppet foo.pp 
notice: Scope(Class[A]): bbleh
notice: Scope(Class[A]): bleh

Which is what I expected. However with 2.6.3 it produces the following output:

# puppet foo.pp 
notice: Scope(Class[A]): false

Imho likely the changes for #4778 and #5074 are responsible for that behavior change.

However this makes it impossible to overwrite parameters of a “included” parametrized class in a subclass. There are only ugly workarounds for that problem and I think this should actually work as it did within 2.6.1. Otherwise the usefulness of parametrized classes is quite reduced.


Related issues

related to Puppet - Bug #4778: Evaluation of classes instantiated using parameterized st... Closed 09/15/2010
related to Puppet - Bug #9259: Subclass attempting to override a parameterized class's p... Needs More Information 08/30/2011
related to Puppet - Bug #10972: Classes can be declared with "class {..." and "include ..... Accepted 11/20/2011
duplicated by Puppet - Bug #7890: Cannot override class parameters Duplicate 06/12/2011

History

Updated by James Turnbull over 1 year ago

  • Status changed from Unreviewed to Needs Decision
  • Assignee set to Nigel Kersten
  • Target version set to 2.6.5

I agree with Peter. Ugly pants.

Updated by James Turnbull over 1 year ago

  • Category set to language

Updated by Nigel Kersten over 1 year ago

  • Status changed from Needs Decision to Accepted

Updated by Nigel Kersten over 1 year ago

  • Assignee deleted (Nigel Kersten)

Updated by Nigel Kersten over 1 year ago

  • Target version changed from 2.6.5 to 2.6.x

Updated by James Turnbull over 1 year ago

  • Target version changed from 2.6.x to 2.6.6

Updated by Nigel Kersten over 1 year ago

  • Priority changed from Normal to High

Updated by James Turnbull about 1 year ago

  • Target version changed from 2.6.6 to 2.6.x

Updated by Andrew Forgue 12 months ago

  • Keywords set to parameterized_classes

Updated by Nick Lewis 11 months ago

  • Status changed from Accepted to Investigating

So the change that broke this is eagerly evaluating parameterized classes. This is necessary because lazily evaluating with one syntax, class { foo: }, and eagerly evaluating with the other, include foo, causes inconsistent behavior.

Given that we eagerly evaluate parameterized classes, I can’t conceive of a model of evaluation that could cause this to work correctly. Essentially the first thing we end up evaluating is class a, at which point we immediately execute the notices, because they’re functions. Changing the notices to notify resources can reconcile this behavior in the presence of futures, should we ever choose to implement those. But for notices, I can’t see a way this could ever work reasonably.

This also points to a deeper incongruence between futures (which are implicitly lazy) and non-rvalue functions (which are implicitly eager and therefore order-dependent).

Do you have a concrete use case for this? There may be another way to accomplish the desired behavior, or at least to provide some mechanism to accomplish it.

Updated by Andrew Forgue 11 months ago

Nick Lewis wrote:

So the change that broke this is eagerly evaluating parameterized classes. This is necessary because lazily evaluating with one syntax, class { foo: }, and eagerly evaluating with the other, include foo, causes inconsistent behavior.

Given that we eagerly evaluate parameterized classes, I can’t conceive of a model of evaluation that could cause this to work correctly. Essentially the first thing we end up evaluating is class a, at which point we immediately execute the notices, because they’re functions. Changing the notices to notify resources can reconcile this behavior in the presence of futures, should we ever choose to implement those. But for notices, I can’t see a way this could ever work reasonably.

Why are parameterized classes eagerly evaluated?

Do you have a concrete use case for this? There may be another way to accomplish the desired behavior, or at least to provide some mechanism to accomplish it.

The use case I was working on is when using an ENC. We use LDAP as our ENC (which doesn’t support specifying parameters for classes included by it. That’s the first problem. That being said, we essentially have the following:

node default 
{
  class { "autofs::sysconfig": basedn => "ou=xxx,o=yyy", master_map => "some_master_map_name" }
}

but then some class of machines call them “development” uses different settings for the based and master map.

class development_node
{
  class { "autos::sysconfig": based => "ou=xxx,o=yyy", master_map => "development_master" }
}

You can’t do this with parameterized classes. Both classes will be included and puppet will fail. So the first hack is this:

node default
{
  include autofs::sysconfig::default
}

class development_node
{
  include autofs::sysconfig::development
}

class autofs::sysconfig::default
{
  class { "autofs::sysconfig": basedn => "ou=xxx,o=yyy", master_map => "some_master_map_name" }
}

class autofs::sysconfig::development inherits autos::sysconfig::default 
{
  Class["autos::sysconfig"] { master_map_name => "development_master" }
}

This doesn’t work because of this bug. It doesn’t really make much sense, other resources can have their attributes overridden by sub classes, it is logical and, I think, reasonable to assume that classes can as well (and since this used to work in 2.6.½ it’s a clear regression). Classes even use the exact same syntax as other resources, so not being able to override will be very confusing. Also, I believe parameterized classes are just about useless if this isn’t possible.

Another use case that is almost identical to the above are tuning parameters. We have tuning parameters for our base install, but the presence of another class in our ENC causes some tuning parameters to be changed. Using parameterized classes with the ability to override solves this problem straightforward and cleanly. Without it (as we do now), is a messy amalgamation of if !defined(Class["hpc_node"]) or crazy scope.lookupvar() in template evaluation. It’s a mess.

I believe what you will say to fix this problem is to use Extlookup. We don’t use ext lookup for a few reasons.

  1. LDAP is our central source of ‘truth’ in our environment, this is a matter of policy and won’t be changed, storing configuration details for nodes outside of LDAP is not something that we want to do.
  2. extlookup isn’t flexible enough. I am looking forward to the work RI is doing with Heira, which may help, but it still comes back to problem number 1 (above).

At the end of the day, we here think it’s a failure in the puppet language that it can’t support this kind of override. As bad as parameterized classes have turned out to be, this is just the nail in the coffin for using them. I don’t know what we’ll do with scope changes in 2.7 and the global variable mess we have since the 0.16.x (?) days.

If what I’m trying to do can be accomplished in a better way I am totally open to hearing about them and being schooled on puppet DSL or systems management philosophy.

Updated by Jacob Helwig 11 months ago

  • Assignee set to Nick Lewis

Updated by Jonathon Anderson 11 months ago

Nick Lewis wrote:

Do you have a concrete use case for this? There may be another way to accomplish the desired behavior, or at least to provide some mechanism to accomplish it.

I’m trying to do this to pass variables to a templated file in the parameterized class. I have class {'ntp': servers => [list, of, servers] }, later overridden as Class['ntp'] { servers => [other, list, of, servers] }. In this use case, I always see the original list of servers in the eventual ntp.conf.

Andrew Forgue wrote:

other resources can have their attributes overridden by sub classes, it is logical and, I think, reasonable to assume that classes can as well (and since this used to work in 2.6.½ it’s a clear regression).

I share this reasoning.

Updated by Sean Millichamp 10 months ago

I also have a very similar use case, prompted by the deprecation of dynamic scoping in 2.7.x which I make heavy use of to accommodate a number of variances required in our particular environment. If I could override parameters in included paramaterized classes I believe I could develop a fairly elegant option to replace what I depend on dynamic scoping for.

I feel like Puppet is heading in a direction that is taking away options without providing reasonable alternatives. There is always the ENC route, but to re-implement what I currently do within the Puppet DSL with dynamic variable scoping (or what I could do if I could override parameters to parameterized classes) seems to require that I am going to need to write my own ENC (neither Puppet Dashboard nor The Foreman seem to currently support the features/flexibility I’d need)

Updated by Nate Przybyszewski 9 months ago

I am still experiencing this problem with 2.7.3. Is there any recommended workaround for this?

Updated by Josh Cooper 6 months ago

  • Status changed from Investigating to Accepted

Git bisect shows this issue was broken in commit:65ef24e5c1c33b7d42012891d368917fd6aaf68c first released in 2.6.3

Updated by Josh Cooper 6 months ago

Andrew Forgue wrote:

Nick Lewis wrote:

Given that we eagerly evaluate parameterized classes, I can’t conceive of a model of evaluation that could cause this to work correctly. Essentially the first thing we end up evaluating is class a, at which point we immediately execute the notices, because they’re functions. Changing the notices to notify resources can reconcile this behavior in the presence of futures, should we ever choose to implement those. But for notices, I can’t see a way this could ever work reasonably.

Why are parameterized classes eagerly evaluated?

To fix this #4778. ‘include’ causes the contents of the class to be evaluated immediately, but evaluation of ‘class { foo: }’ was being deferred. The fix for #4778 changed the latter to be eagerly evaluated (to be consistent with the former), but in doing so broke the ability to override included parameterized classes.

Updated by Josh Cooper 6 months ago

If you eliminate inheritance, then the following will achieve the same effect and works in the current 2.6.x and 2.7.x branches:

node default {
  include autofs::sysconfig::default
}

class development_node {
  include autofs::sysconfig::development
}

class autofs::sysconfig::common($master_map) {
  class { 'autofs::sysconfig':
    basedn => "ou=xxx,o=yyy",
    master_map => $master_map
  }
}

class autofs::sysconfig::default {
  class { 'autofs::sysconfig::common':
    master_map => 'some_master_map_name'
  }
}

class autofs::sysconfig::development {
  class { 'autofs::sysconfig::common':
    master_map => 'development_master'
  }
}

Updated by Andrew Forgue 6 months ago

Sorry Josh, I’ve since thrown away the code since I couldn’t get it to work.

How would your example work with both autofs::sysconfig::default and autofs::sysconfig::development including class { ‘autofs::sysconfig::common’: }. I didn’t think that was allowed with parameterized classes.

Updated by Josh Cooper 5 months ago

How would your example work with both autofs::sysconfig::default and autofs::sysconfig::development including class { ‘autofs::sysconfig::common’: }. I didn’t think that was allowed with parameterized classes.

According to the language guide, “Classes with parameters are not declared using the include function but with an alternate syntax similar to a resource declaration.” And it gives the example of:

node webserver {
  class { 'apache': version => '1.3.13' }
}

I think the same applies in your situation, you should declare something like:

class autofs::sysconfig::development {
  class { 'autofs::sysconfig::common':
    master_map => 'development_master'
  }
}

Where class development is declaring class common and specifying the master_map parameter to pass to the latter.

Updated by Michael Stahnke 4 months ago

  • Target version changed from 2.6.x to 2.7.x

2.6.x is closed. Moving to 2.7.x

Updated by Matt Keating 3 months ago

I’m still having this issue in 2.7.9. I can’t redo the classes like your suggestion with autofs::sysconfig::development, as I override different parameters from different subclasses…

Also available in: Atom PDF