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.

Reply via email to