Zed
Zed

Reputation: 3517

Puppet 4: how to add calling class variables to scope of a defined type

In Puppet 3 and prior, templates in defines inherited scope from their calling class the same way native defined types do, so if I had a file resource with a template source created by a define, that template could make use of variables set in the calling class no matter which class called the define.

In Puppet 4 (also tested with Puppet 3.8 future parser), that appears to no longer be the case, and it is causing breakage that is hard to even measure in my environment, which has relied on this behavior for tens of thousands of lines of code. Is there any way at all to get this back? After looking into it, even converting the defines into native types doesn't solve the problem, as they rely on the ability to gather server-side information about what templates are available in different modules via custom functions, and everything in a native resource type appears to happen on the client.

Is this fixable, or do I attempt to wait for Puppet 5?

Edit: Don't get too caught up in the word 'scope' here -- I need any way to pass all class variables to a define and unpack them there so that they are available to templates, or a way to have a native type see what files are inside specified modules on the puppetmaster. I will accept any bizarre, obscure message passing option as long as it has this result, because the classes do not know where the templates are -- only the define does, because it's making use of the helper functions that scan the filesystem on the server.

Edit 2: To prove this works as expected in Puppet 3.8.5, use the following:

modules/so1/manifests/autotemplate.pp:

# Minimal define example for debugging
define so1::autotemplate (
    $ensure = 'present',
    $module = $caller_module_name,
) {
    $realtemplate = "${module}${title}"

    file { $title :
        ensure  => $ensure,
        owner   => 'root', group => 'root', mode => '0600',
        content => template($realtemplate),
    }
}

in modules/so2/manifests/example.pp:

# Example class calling so1::autotemplate
class so2::example (
    $value = 'is the expected value'
) {
    so1::autotemplate { '/tmp/qwerasdf': }
}

in modules/so2/templates/tmp/qwerasdf:

Expected value: <%= @value %>

In Puppet 3.8.5 with future parser off, this results in /tmp/qwerasdf being generated on the system with the contents:

Expected value: is the expected value

In Puppet 3.8.5. with parser = future in environment.conf (and also Puppet 4.x, though I tested for this example specifically on a 3.8.5 future parser environment), this results in the file being create with the contents:

Expected value:

Edit 3: two-word touch-up for precision

Upvotes: 1

Views: 2879

Answers (1)

John Bollinger
John Bollinger

Reputation: 180093

In Puppet 3 and prior, defines inherited scope from their calling class the same way native defined types do, so if I had a file resource with a template source created by a define, that template could make use of variables set in the calling class no matter which class called the define.

What you're describing is Puppet's old dynamic scoping. The change in scoping rules was one of the major changes in Puppet 3.0; it is not new in Puppet 4. There was, however, a bug in Puppet 3 that templates still used dynamic scope. That was fixed in Puppet 3.5, but only prospectively -- that is, when the future parser is enabled. Defined types themselves went through the scoping change in Puppet 3.0.0, along with everything else. The scope changes were a big deal (and Puppet devoted considerable effort to alerting users to them) when they first went into place, but nowadays there's no big deal here.

it is causing breakage that is hard to even measure in my environment, which has relied on this behavior for tens of thousands of lines of code.

I'm sorry you're having that experience.

Is there any way at all to get this back?

No. Puppet scoping rules do not work the way you want them to do. That they did work that way in templates (but not most other places) in Puppet 3 was and still is contrary to Puppet's documentation, and never intentional.

Is this fixable, or do I attempt to wait for Puppet 5?

There is no way to get dynamic variable scoping in templates or elsewhere in Puppet 4, and I have no reason to think that there will be one in Puppet 5.

If you need a template to expand host variables from the scope of a particular class, then you can get that by evaluating the template in the scope of that class. Alternatively, an ERB template can obtain variables from (specific) other scopes by means of the scope object. On the other hand, if you want to expand the template in the scope of a defined type, then you could consider passing the needed variables as parameters of that type.

There may be other ways to address your problem, but I'd need more details to make any other suggestions. (And that would constitute a separate question if you choose to ask it here on SO.)

Upvotes: 1

Related Questions