Resource chaining is not being honored

19 views
Skip to first unread message

Johnathan Komara

unread,
Mar 8, 2018, 5:53:27 PM3/8/18
to Puppet Users
Description

I am unable to get class 'profile::b' to run before class 'profile::a'.

Setup

I have two classes that create files, class 'profile::a' and class 'profile::b'. 

I am following the roles and profiles setup. I have a base profile where I am including 'profile::a':

class profile::base {
  include
'modules'
  include
'::profile::a'
}


Then I have a class role include the base profile

class role {
  include
'profile::base'
}


And then I have our server roles inherit role:

class role::test_server inherits ::role {
  include
'::profile::b'
}


'profile::a'

class profile::a {
 
if defined(File['/etc/test.txt']) {
    $var
= 'blah1.txt'
 
} else {
    $var
= 'blah2.txt'
 
}

 
File {$var :
   
ensure => file,
 
}
}


'profile::b'

class profile::b {
 
File { '/etc/test.txt' :
   
ensure => file,
    owner  
=> 'root',
   
group  => 'root',
}


Problem

'profile::a' should be applied to every server and 'profile::b' is only being applied to a few servers. My understanding of resource chaining is that I can alter the order in which the classes are ran. I need "b" to run first so that I can key off of "defined(File['/etc/test/txt']).

I have tried multiple approaches and I cannot get this to work.

#1

class profile::b {
 
File { '/etc/test.txt' :
   
ensure => file,
    owner  
=> 'root',
   
group   => 'root',
    before  
=> Class['profile::a'],
}

#2


class profile::b {
 
File { '/etc/test.txt' :
   
ensure => file,
    owner  
=> 'root',
   
group   => 'root',
 
}

File['/etc/test.txt'] -> Class['profile::a']
}

#3
class profile::a {
 
if defined(File['/etc/test.txt']) {
    $var
= 'blah1.txt'
 
} else {
    $var
= 'blah2.txt'
 
}

 
File {$var :
   
ensure => file,
 
}

 
File['/etc/test.txt'] -> Class['profile::a']
}

#4
class profile::a {
 
if defined(File['/etc/test.txt']) {
    $var
= 'blah1.txt'
 
} else {
    $var
= 'blah2.txt'
 
}

 
File {$var :
   
ensure => file,
 
}

 
File<| title == '/etc/test.txt' |> -> Class['profile::a']
}

The only way that it works is if I explicitly include 'profile::b' before 'profile::a' in base. 

class profile::base {
  include
'modules'
  include
'::profile::b'
  include
'::profile::a'
}

Or if I remove 'profile::a' from base and include them both in the role.

class role::test_server inherits ::role {
  include
'::profile::b'
  include
'::profile::a'
}


I have alternatives. I can set values in hiera to key off of or push out custom facts. Custom facts will not be there on the first run which is probably ok. I think we would run into a few issues here and there but I could make it work. I could set hiera vars and key off of those instead but with our hiera setup that would require duplicating a lot of code across every role.

Everything I have read says that chaining the resource should enforce the order but I am obviously missing something. Can someone see what I am doing wrong or explain why this will not work?

Thank you in advance.

jcbollinger

unread,
Mar 9, 2018, 9:52:19 AM3/9/18
to Puppet Users


On Thursday, March 8, 2018 at 4:53:27 PM UTC-6, Johnathan Komara wrote:
 
'profile::a' should be applied to every server and 'profile::b' is only being applied to a few servers. My understanding of resource chaining is that I can alter the order in which the classes are ran. I need "b" to run first so that I can key off of "defined(File['/etc/test/txt']).


No, you don't, and you shouldn't.  You should not use the defined() function at all, in fact, because it produces evaluation-order dependencies, which is exactly what you are dealing with.  You will find considerable discussion of this topic in the archives of this group, including several previous assertions on my part that defined() is evil.

Additionally, you are conflating two different orderings: order of manifest evaluation on the master and order of catalog application on the target node.  Resource relationships, whether established via the chain operators or via the appropriate resource metaparameters,affect only the latter, whereas defined(), being evaluated during catalog building, is affected only by the former.
 

I have tried multiple approaches and I cannot get this to work.


I'm not at all surprised, since even if you are determined to use defined() despite my advice, you're trying to use the wrong tool for the job of getting the evaluation order you need to make it work.  Evaluation order depends primarily on the contents and lexical order of your manifests.  It is extremely difficult to predict, and if you have caching enabled in the relevant environment then you're pretty much toast.

 
The only way that it works is if I explicitly include 'profile::b' before 'profile::a' in base. 

class profile::base {
  include
'modules'
  include
'::profile::b'
  include
'::profile::a'
}

Or if I remove 'profile::a' from base and include them both in the role.

class role::test_server inherits ::role {
  include
'::profile::b'
  include
'::profile::a'
}


As I said, the contents and lexical order of your manifests.  But even that may not be reliable.  Classes can be included multiple times, from multiple locations, and if you have another location where you include ::profile::a before or without including ::profile::b, then your result will depend which declaration of ::profile::a is evaluated first.
 

I have alternatives. I can set values in hiera to key off of or push out custom facts.


And that is what you indeed should do instead.  There might be room for an accompanying refactoring of your manifest set to go along with it and make things smoother, but you'll need to determine that for yourself.

Moreover, you might not need to add anything in the first place.  You already have logic some place to determine whether to assign role::test_server to your nodes.  It seems that's the same thing you want to rely on here.

 
Custom facts will not be there on the first run which is probably ok. I think we would run into a few issues here and there but I could make it work.


Custom facts could work too, but it is best for the target configuration of your nodes to be determined by their identities and invariant characteristics, based on centrally-maintained manifests and data.

 
Everything I have read says that chaining the resource should enforce the order but I am obviously missing something. Can someone see what I am doing wrong or explain why this will not work?


In addition to what I've already said, the fact that you're using class inheritance has bad code smell, and in practice it contributes to your problem.

Class inheritance is an ancient feature of the language that was originally intended to provide for overriding the parameters of resources declared by the parent class.  It still works for that, but Puppet has other mechanisms for that purpose, and for avoiding the need for overrides in the first place, and nowadays those are stylistically preferred.  Class inheritance also has the side effect of causing the parent class to be evaluated before the child class, which is what makes the params class pattern work, and that pattern is the only widely accepted use for class inheritance in modern Puppet.

In OO-speak, prefer composition over inheritance. Very much more so in Puppet DSL -- which looks a lot more like an OO language than it really is -- than in general-purpose OO programming languages, such Java or C++.


John

Johnathan Komara

unread,
Mar 9, 2018, 10:40:02 AM3/9/18
to Puppet Users
I appreciate you taking the time to review my post and give such a thorough answer.

Thank you,

Johnathan
Reply all
Reply to author
Forward
0 new messages