On Thursday, September 18, 2014 5:59:56 PM UTC-5, François Lafont wrote:
>
> Hi,
>
> Le 18/09/2014 16:40, jcbollinger a écrit :
>
>
> So, finally, is this way below a correct and good way to use the "params"
> pattern?
>
> -----------------------------------------------------
> # Example of declaration for the "test" class.
> class { test:
> var1 => 'specific_var1',
> }
>
> #### test module with its classes ####
> class test::params {
> # API
> $var1 = 'default_var1'
> $var2 = 'default_var2'
>
> # Shared variables
> $foo1 = 'value_foo1'
> $foo2 = 'value_foo2'
> }
>
> # The main class of the module
>
> # I use fully qualified variable names exclusively!
> class test (
> $var1 = $::test::params::var1,
> $var2 = $::test::params::var2,
> ) inherits test::params {
>
> include ::test::internal
>
> notify { 'v1': message => "var1 = $::test::var1", }
> notify { 'v2': message => "var2 = $::test::var2", }
> notify { 'f1': message => "foo1 = $::test::foo1", }
> notify { 'f2': message => "foo2 = $::test::foo2", }
>
> }
>
The main points I am trying to get across are these:
1. References to *non-local* variables should always be performed via
qualified names, even when class inheritance makes the variable in question
accessible also via its unqualified name.
2. All qualified names used should accurately reflect the location of
the referenced entity's declaration (some variables have multiple, distinct
qualified names).
3. If a class or defined type refers to a variable belonging to a
different class then it should ensure via 'include' / 'require' / 'contain'
or via class inheritance that the variable is initialized first.
I am not saying that references to variables declared *locally* should use
qualified names, but if you want to adopt that practice then it's fine.
With respect to other classes' variables, though, I seem not to be
successfully communicating point (2). There are two formulations for your
class test that I would consider good form, neither one exactly what you
presented above.
*Alternative 1:*
----
class test (
$var1 = $::test::params::var1,
$var2 = $::test::params::var2,
) inherits test::params {
include ::test::internal
notify { 'v1': message => "var1 = *$var1*", }
notify { 'v2': message => "var2 = *$var2*", }
notify { 'f1': message => "foo1 = *$::test::params::foo1*", }
notify { 'f2': message => "foo2 = *$::test::params::foo2*", }
}
----
*Alternative 2:*
----
class test (
$var1 = $::test::params::var1,
$var2 = $::test::params::var2,
) inherits test::params {
include ::test::internal
notify { 'v1': message => "var1 = $::test::var1", }
notify { 'v2': message => "var2 = $::test::var2", }
notify { 'f1': message => "foo1 = *$::test::params::foo1*", }
notify { 'f2': message => "foo2 = *$::test::params::foo2*", }
}
----
I would also be ok with using qualified but not absolute variable
references of the form "$test::params::foo1", but the absolute versions you
presented are slightly safer.
Either way, the only variables "foo1" and "foo2" in the module belong to
class test::params. All references to them should be relative to their
declaring class (test::params), notwithstanding the fact that because of
class inheritance, Puppet should still find them if you instead qualify
them with namespace "test::".
>
> class test::internal {
>
> notify { 'iv1': message => "Internal var1 = $::test::var1", }
> notify { 'iv2': message => "Internal var2 = $::test::var2", }
> notify { 'if1': message => "Internal foo1 = $::test::foo1", }
> notify { 'if2': message => "Internal foo2 = $::test::foo2", }
>
>
As above, references to variables foo1 and foo2 should be qualifed by
namespace 'test::params', not namespace 'test'.
Furthermore, this class definition above does not ensure that variables
$::test::var1, $::test::var2, $::test::params::foo1, and
$::test::params::foo2 have been initialized. If it is in fact declared
only by class test then that will work out, because class test provides
those assurances. Generally speaking, it would be better form for class
test::inner to take that into its own hands, as doing so would make the
class both more flexible and more self-documenting. In this particular
case, however, that would involve class test::inner 'include'ing class
test, and that's not sensible when class test is also including
test::inner.
So, that's mostly OK, with the caveats that references to foo1 and foo2
should be differently qualified, and that class test::inner is encumbered
with usage restrictions.
> # So, in templates, I use the syntax scope['::test::var']
> # (I'm using Puppet 3.7.1 so -> scope[...])
> file { '/temp/internal.txt':
> content => inline_template("var1=<%= scope['::test::var1']
> %>\nfoo2=<%= scope['::test::foo2'] %>\n"),
> }
>
Again, qualify references to foo2 more appropriately:
file { '/temp/internal.txt':
content => inline_template("var1=<%= scope['::test::var1'] %>\nfoo2=<%=
scope['::test::params::foo2'] %>\n"),
}
> }
> -----------------------------------------------------
>
> I use $::test::xxx in class::internal not $::test::params::xxx
> because when xxx is "var1" or "var2", I want to have the value of
> var1 and var2 given as parameters of the test class (I don't want
> the default value of var1 and var2).
>
Yes, that's fine for var1 and var2, but not for foo1 and foo2 which are not
declared by class test. I don't see any particular purpose for foo1 and
foo2 to live in test::params anyway, however, if they are not to be used as
class parameter defaults. You could as easily put them directly in class
test, and then continue to refer to them as you are now doing.
> > No, not in the slightest. I mean that if class my_module::params has a
> > local variable with unqualified name "foo1", then that variable can be
> > referenced from any class in any module as $my_module::params::foo1,
> > regardless of any inheritance or include / require / contain statements
> > anywhere.
>
> Hum... there is exception, isn't? For instance, it depends of the
> order of the class in the catalog, I'm wrong?
>
>
It used to be that the *value* to which the variable reference resolves
depends on the order in which classes are evaluated (it would be nil if the
declaring class is not evaluated before the variable reference), but the
reference itself would be accepted without any kind of error, regardless of
evaluation order. Puppet's evaluator is getting smarter, but that doesn't
invalidate my claim.
If the class evaluation order -- which is often difficult to predict --
happens to be such that no class variable is referenced before its class is
evaluated, then all will be well. That can happen without any class
inheritance or any use of 'include' / 'require' / 'contain', as I said. My
point is that you should use 'include' / 'require' / 'contain', or
occasionally class inheritance, to *ensure* a suitable class evaluation
order, and that that is those statements' primary purpose as it pertains to
variables.
> For instance, with this manifest:
>
> -----------------------------------------------------
> include ::test1
> include ::test2
>
> class test1 {
> $var1 = "var1"
> notify { 'm1': message => "var2 = $::test2::var2", }
> }
>
> class test2 {
> $var2 = "var2"
> notify { 'm2': message => "var1 = $::test1::var1", }
> }
> -----------------------------------------------------
>
> I have this result:
>
> ~$ puppet apply manifest.pp
> Warning: Scope(Class[Test1]): Could not look up qualified variable
> '::test2::var2'; class ::test2 has not been evaluated
> Notice: Compiled catalog for wheezy-clean.chezmoi.priv in environment
> production in 0.02 seconds
> Notice: var1 = var1
> Notice: /Stage[main]/Test2/Notify[m2]/message: defined 'message' as 'var1
> = var1'
> Notice: var2 =
> Notice: /Stage[main]/Test1/Notify[m1]/message: defined 'message' as 'var2
> = '
> Notice: Finished catalog run in 0.41 seconds
>
>
And the appropriate solution to that problem is putting "include 'test2'"
at the beginning of the body of class test1.
By the way, don't be confused about the meaning of the 'include' statement,
or its siblings 'require' and 'contain'. 'Include' says that the named
class must be included *in the catalog*; it does NOT interpolate the named
class's body in place of the include statement (i.e. it is not analogous to
a C preprocessor #include directive). A given class can be the subject of
any number of 'include', 'require', and 'contain' statements; no class will
appear in the catalog more than once.
> >> I can give you a precise and quick (if you have already a Debian
> Wheezy)
> >> process to reproduct this. I take a Debian Wheezy, updated and cleaned
> >> (it's a simple VM in Virtualbox). And here is the process:
> >>
> >>
> >
> > I'm not prepared to set up and run this test,
>
> Ok. Let me show you an even faster test than the previous test (no
> installation
> of puppetmaster just the puppet agent) :
>
>
> root@wheezy-clean:~# lsb_release -sc
> wheezy
>
> root@wheezy-clean:~# wget
> https://apt.puppetlabs.com/puppetlabs-release-$(lsb_release -sc).deb
> root@wheezy-clean:~# dpkg -i puppetlabs-release-$(lsb_release -sc).deb
> root@wheezy-clean:~# apt-get update
> root@wheezy-clean:~# apt-get install puppet
> root@wheezy-clean:~# puppet -V
> 3.7.1
>
> # Just comment the "templatedir" line in puppet.conf (which is deprecated)
> root@wheezy-clean:~# sed -i -r 's/^(templatedir.*)$/#\1/g'
> /etc/puppet/puppet.conf
>
> # Create a simple manifest.
> root@wheezy-clean:~# vim manifest.pp
>
> Here is my (very) simple manifest:
>
> ------------------------------
> include ::test
>
> class test {
> $var1 = "value_var1" # LINE A
> include ::test::internal # LINE B
> }
>
> class test::internal {
> notify { 'n1': message => "var1 = $var1", }
>
> file { '/tmp/internal.txt':
> content => inline_template("var1 = <%= @var1 %>\n"),
> }
> }
> ------------------------------
>
> # Apply the manifest.
> root@wheezy-clean:~# puppet apply manifest.pp
> Notice: Compiled catalog for wheezy-clean.chezmoi.priv in environment
> production in 0.06 seconds
> Notice: /Stage[main]/Test::Internal/File[/tmp/internal.txt]/ensure:
> defined content as '{md5}90e1f6ab3dc65719105f56f3b58dee5d'
> Notice: var1 =
> Notice: /Stage[main]/Test::Internal/Notify[n1]/message: defined 'message'
> as 'var1 = '
> Notice: Finished catalog run in 0.15 seconds
>
> root@wheezy-clean:~# cat /tmp/internal.txt
> var1 = value_var1
>
> As you can see, in the local scope of test::internal, there is
> no defined "var1" (and notify prints "var1 =") but in the
> template, @var1 has been replaced by "value_var1".
>
> And now, if I just swap LINE A with LINE B, I have this:
>
> root@wheezy-clean:~# puppet apply manifest.pp
> Notice: Compiled catalog for wheezy-clean.chezmoi.priv in environment
> production in 0.06 seconds
> Notice: /Stage[main]/Test::Internal/File[/tmp/internal.txt]/content:
> content changed '{md5}90e1f6ab3dc65719105f56f3b58dee5d' to
> '{md5}62d0ae15ddb3f27d78aa9a04c7c9cc4d'
> Notice: var1 =
> Notice: /Stage[main]/Test::Internal/Notify[n1]/message: defined 'message'
> as 'var1 = '
> Notice: Finished catalog run in 0.03 seconds
>
> root@wheezy-clean:~# cat /tmp/internal.txt
> var1 =
>
> Honestly, I don't understand. Is it normal, is it a bug... I don't know.
>
>
In part, it looks like you are running into bug 1220
<https://tickets.puppetlabs.com/browse/PUP-1220>. That was reported fixed
in Puppet 3.5.0, but if I understand correctly, you need to enable the
future parser to actually activate the bug fix (which would make the
variable reference in your template fail to resolve in both cases).
> And in this case, I don't understand. I thought that :
>
> $var1 = "value_var1" # LINE A
> include ::test::internal # LINE B
>
> and
>
> include ::test::internal # LINE B
> $var1 = "value_var1" # LINE A
>
> was completely equivalent. Am I wrong?
>
>
Yes, you are wrong, but the reason is a bit subtle. For a comprehensive
explanation, see
http://puppet-on-the-edge.blogspot.com/2014/04/getting-your-puppet-ducks-in-row.html
.
The short(ish) answer is that although the catalog Puppet constructs based
on your manifests is declarative and order-insensitive, the process of
evaluating your manifests to produce that catalog proceeds in and is
subject to a well-defined (but difficult to predict) order. In particular,
when the evaluator encounters an 'include' statement naming a class that
has not yet been evaluated, it immediately suspends evaluating the current
class and evaluates the named class. Statements later in the body of the
original class will not yet have been evaluated during the evaluation of
the included class. This behavior is necessary and appropriate, as it
allows, for instance, variables of the 'include'd class to be used in the
definitions of variables of the including class.
More generally, your code is suffering from problems associated with
bidirectional dependency. It is fine for class test to declare class
test::inner. It is fine for class test::inner to refer to variables of
class test (given some mechanism ensuring that class test will have been
evaluated). It is problematic -- but not altogether impossible -- to have
both at the same time. You really ought to consider externalizing your
data and accessing via Hiera. Though I'm not a big fan of class
parameterization, you could also consider parameterizing class test::inner
and using those parameters to give it the values it needs.
John
--
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 [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/puppet-users/f42c0a97-c6c2-4434-b1ff-475191ba2dcd%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.