Bug #5517
behavior change within 2.6 makes it impossible to override class parameters of "included" parametrized classes
| Status: | Accepted | Start date: | 12/12/2010 | |
|---|---|---|---|---|
| Priority: | High | Due date: | ||
| Assignee: | % 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
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.
- 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.
- 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…